mirror of
				https://github.com/kyantech/Palmr.git
				synced 2025-11-04 05:53:23 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			next
			...
			v3.2.2-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f3aeaf66df | ||
| 
						 | 
					331624e2f2 | ||
| 
						 | 
					ba512ebe95 | 
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -6,6 +6,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
**Palmr.** is a **flexible** and **open-source** alternative to file transfer services like **WeTransfer**, **SendGB**, **Send Anywhere**, and **Files.fm**.
 | 
					**Palmr.** is a **flexible** and **open-source** alternative to file transfer services like **WeTransfer**, **SendGB**, **Send Anywhere**, and **Files.fm**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div align="center">
 | 
				
			||||||
 | 
					  <div style="background: linear-gradient(135deg, #ff4757, #ff3838); padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 4px 15px rgba(255, 71, 87, 0.3); border: 2px solid #ff3838;">
 | 
				
			||||||
 | 
					    <h3 style="color: white; margin: 0 0 10px 0; font-size: 18px; font-weight: bold;">
 | 
				
			||||||
 | 
					      ⚠️ BETA VERSION
 | 
				
			||||||
 | 
					    </h3>
 | 
				
			||||||
 | 
					    <p style="color: white; margin: 0; font-size: 14px; opacity: 0.95;">
 | 
				
			||||||
 | 
					      <strong>This project is currently in beta phase.</strong><br>
 | 
				
			||||||
 | 
					      Not recommended for production environments.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
🔗 **For detailed documentation visit:** [Palmr. - Documentation](https://palmr.kyantech.com.br)
 | 
					🔗 **For detailed documentation visit:** [Palmr. - Documentation](https://palmr.kyantech.com.br)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "palmr-docs",
 | 
					  "name": "palmr-docs",
 | 
				
			||||||
  "version": "3.2.1-beta",
 | 
					  "version": "3.2.2-beta",
 | 
				
			||||||
  "description": "Docs for Palmr",
 | 
					  "description": "Docs for Palmr",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
					  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "palmr-api",
 | 
					  "name": "palmr-api",
 | 
				
			||||||
  "version": "3.2.1-beta",
 | 
					  "version": "3.2.2-beta",
 | 
				
			||||||
  "description": "API for Palmr",
 | 
					  "description": "API for Palmr",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
					  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import bcrypt from "bcryptjs";
 | 
				
			||||||
import { FastifyReply, FastifyRequest } from "fastify";
 | 
					import { FastifyReply, FastifyRequest } from "fastify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { env } from "../../env";
 | 
					import { env } from "../../env";
 | 
				
			||||||
@@ -183,6 +184,7 @@ export class FileController {
 | 
				
			|||||||
        objectName: string;
 | 
					        objectName: string;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      const objectName = decodeURIComponent(encodedObjectName);
 | 
					      const objectName = decodeURIComponent(encodedObjectName);
 | 
				
			||||||
 | 
					      const { password } = request.query as { password?: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!objectName) {
 | 
					      if (!objectName) {
 | 
				
			||||||
        return reply.status(400).send({ error: "The 'objectName' parameter is required." });
 | 
					        return reply.status(400).send({ error: "The 'objectName' parameter is required." });
 | 
				
			||||||
@@ -193,6 +195,51 @@ export class FileController {
 | 
				
			|||||||
      if (!fileRecord) {
 | 
					      if (!fileRecord) {
 | 
				
			||||||
        return reply.status(404).send({ error: "File not found." });
 | 
					        return reply.status(404).send({ error: "File not found." });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let hasAccess = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.log("Requested file with password " + password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const shares = await prisma.share.findMany({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          files: {
 | 
				
			||||||
 | 
					            some: {
 | 
				
			||||||
 | 
					              id: fileRecord.id,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        include: {
 | 
				
			||||||
 | 
					          security: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const share of shares) {
 | 
				
			||||||
 | 
					        if (!share.security.password) {
 | 
				
			||||||
 | 
					          hasAccess = true;
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        } else if (password) {
 | 
				
			||||||
 | 
					          const isPasswordValid = await bcrypt.compare(password, share.security.password);
 | 
				
			||||||
 | 
					          if (isPasswordValid) {
 | 
				
			||||||
 | 
					            hasAccess = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!hasAccess) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await request.jwtVerify();
 | 
				
			||||||
 | 
					          const userId = (request as any).user?.userId;
 | 
				
			||||||
 | 
					          if (userId && fileRecord.userId === userId) {
 | 
				
			||||||
 | 
					            hasAccess = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } catch (err) {}
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!hasAccess) {
 | 
				
			||||||
 | 
					        return reply.status(401).send({ error: "Unauthorized access to file." });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const fileName = fileRecord.name;
 | 
					      const fileName = fileRecord.name;
 | 
				
			||||||
      const expires = parseInt(env.PRESIGNED_URL_EXPIRATION);
 | 
					      const expires = parseInt(env.PRESIGNED_URL_EXPIRATION);
 | 
				
			||||||
      const url = await this.fileService.getPresignedGetUrl(objectName, expires, fileName);
 | 
					      const url = await this.fileService.getPresignedGetUrl(objectName, expires, fileName);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,7 +108,6 @@ export async function fileRoutes(app: FastifyInstance) {
 | 
				
			|||||||
  app.get(
 | 
					  app.get(
 | 
				
			||||||
    "/files/:objectName/download",
 | 
					    "/files/:objectName/download",
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      preValidation,
 | 
					 | 
				
			||||||
      schema: {
 | 
					      schema: {
 | 
				
			||||||
        tags: ["File"],
 | 
					        tags: ["File"],
 | 
				
			||||||
        operationId: "getDownloadUrl",
 | 
					        operationId: "getDownloadUrl",
 | 
				
			||||||
@@ -117,6 +116,9 @@ export async function fileRoutes(app: FastifyInstance) {
 | 
				
			|||||||
        params: z.object({
 | 
					        params: z.object({
 | 
				
			||||||
          objectName: z.string().min(1, "The objectName is required"),
 | 
					          objectName: z.string().min(1, "The objectName is required"),
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
 | 
					        querystring: z.object({
 | 
				
			||||||
 | 
					          password: z.string().optional().describe("Share password if required"),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
        response: {
 | 
					        response: {
 | 
				
			||||||
          200: z.object({
 | 
					          200: z.object({
 | 
				
			||||||
            url: z.string().describe("The download URL"),
 | 
					            url: z.string().describe("The download URL"),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "palmr-web",
 | 
					  "name": "palmr-web",
 | 
				
			||||||
  "version": "3.2.1-beta",
 | 
					  "version": "3.2.2-beta",
 | 
				
			||||||
  "description": "Frontend for Palmr",
 | 
					  "description": "Frontend for Palmr",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
					  "author": "Daniel Luiz Alves <daniel@kyantech.com.br>",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ interface ShareFilesTableProps {
 | 
				
			|||||||
  onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
 | 
					  onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
 | 
				
			||||||
  onNavigateToFolder?: (folderId: string) => void;
 | 
					  onNavigateToFolder?: (folderId: string) => void;
 | 
				
			||||||
  enableNavigation?: boolean;
 | 
					  enableNavigation?: boolean;
 | 
				
			||||||
 | 
					  sharePassword?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ShareFilesTable({
 | 
					export function ShareFilesTable({
 | 
				
			||||||
@@ -39,6 +40,7 @@ export function ShareFilesTable({
 | 
				
			|||||||
  onDownloadFolder,
 | 
					  onDownloadFolder,
 | 
				
			||||||
  onNavigateToFolder,
 | 
					  onNavigateToFolder,
 | 
				
			||||||
  enableNavigation = false,
 | 
					  enableNavigation = false,
 | 
				
			||||||
 | 
					  sharePassword,
 | 
				
			||||||
}: ShareFilesTableProps) {
 | 
					}: ShareFilesTableProps) {
 | 
				
			||||||
  const t = useTranslations();
 | 
					  const t = useTranslations();
 | 
				
			||||||
  const [isPreviewOpen, setIsPreviewOpen] = useState(false);
 | 
					  const [isPreviewOpen, setIsPreviewOpen] = useState(false);
 | 
				
			||||||
@@ -213,7 +215,14 @@ export function ShareFilesTable({
 | 
				
			|||||||
        </Table>
 | 
					        </Table>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {selectedFile && <FilePreviewModal isOpen={isPreviewOpen} onClose={handleClosePreview} file={selectedFile} />}
 | 
					      {selectedFile && (
 | 
				
			||||||
 | 
					        <FilePreviewModal
 | 
				
			||||||
 | 
					          isOpen={isPreviewOpen}
 | 
				
			||||||
 | 
					          onClose={handleClosePreview}
 | 
				
			||||||
 | 
					          file={selectedFile}
 | 
				
			||||||
 | 
					          sharePassword={sharePassword}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,7 +46,7 @@ interface Folder {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ShareDetailsPropsExtended extends Omit<ShareDetailsProps, "onBulkDownload" | "password"> {
 | 
					interface ShareDetailsPropsExtended extends Omit<ShareDetailsProps, "onBulkDownload"> {
 | 
				
			||||||
  onBulkDownload?: () => Promise<void>;
 | 
					  onBulkDownload?: () => Promise<void>;
 | 
				
			||||||
  onSelectedItemsBulkDownload?: (files: File[], folders: Folder[]) => Promise<void>;
 | 
					  onSelectedItemsBulkDownload?: (files: File[], folders: Folder[]) => Promise<void>;
 | 
				
			||||||
  folders: Folder[];
 | 
					  folders: Folder[];
 | 
				
			||||||
@@ -60,6 +60,7 @@ interface ShareDetailsPropsExtended extends Omit<ShareDetailsProps, "onBulkDownl
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function ShareDetails({
 | 
					export function ShareDetails({
 | 
				
			||||||
  share,
 | 
					  share,
 | 
				
			||||||
 | 
					  password,
 | 
				
			||||||
  onDownload,
 | 
					  onDownload,
 | 
				
			||||||
  onBulkDownload,
 | 
					  onBulkDownload,
 | 
				
			||||||
  onSelectedItemsBulkDownload,
 | 
					  onSelectedItemsBulkDownload,
 | 
				
			||||||
@@ -186,6 +187,7 @@ export function ShareDetails({
 | 
				
			|||||||
            setSelectedFile(null);
 | 
					            setSelectedFile(null);
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
          file={selectedFile}
 | 
					          file={selectedFile}
 | 
				
			||||||
 | 
					          sharePassword={password}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -232,6 +232,7 @@ export function usePublicShare() {
 | 
				
			|||||||
      await downloadShareFolderWithQueue(folderId, folderName, share.files || [], share.folders || [], {
 | 
					      await downloadShareFolderWithQueue(folderId, folderName, share.files || [], share.folders || [], {
 | 
				
			||||||
        silent: true,
 | 
					        silent: true,
 | 
				
			||||||
        showToasts: false,
 | 
					        showToasts: false,
 | 
				
			||||||
 | 
					        sharePassword: password,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      console.error("Error downloading folder:", error);
 | 
					      console.error("Error downloading folder:", error);
 | 
				
			||||||
@@ -253,6 +254,7 @@ export function usePublicShare() {
 | 
				
			|||||||
          downloadFileWithQueue(objectName, fileName, {
 | 
					          downloadFileWithQueue(objectName, fileName, {
 | 
				
			||||||
            silent: true,
 | 
					            silent: true,
 | 
				
			||||||
            showToasts: false,
 | 
					            showToasts: false,
 | 
				
			||||||
 | 
					            sharePassword: password,
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            loading: t("share.messages.downloadStarted"),
 | 
					            loading: t("share.messages.downloadStarted"),
 | 
				
			||||||
@@ -320,9 +322,15 @@ export function usePublicShare() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      toast.promise(
 | 
					      toast.promise(
 | 
				
			||||||
        bulkDownloadShareWithQueue(allItems, share.files || [], share.folders || [], zipName, undefined, true).then(
 | 
					        bulkDownloadShareWithQueue(
 | 
				
			||||||
          () => {}
 | 
					          allItems,
 | 
				
			||||||
        ),
 | 
					          share.files || [],
 | 
				
			||||||
 | 
					          share.folders || [],
 | 
				
			||||||
 | 
					          zipName,
 | 
				
			||||||
 | 
					          undefined,
 | 
				
			||||||
 | 
					          true,
 | 
				
			||||||
 | 
					          password
 | 
				
			||||||
 | 
					        ).then(() => {}),
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          loading: t("shareManager.creatingZip"),
 | 
					          loading: t("shareManager.creatingZip"),
 | 
				
			||||||
          success: t("shareManager.zipDownloadSuccess"),
 | 
					          success: t("shareManager.zipDownloadSuccess"),
 | 
				
			||||||
@@ -387,9 +395,15 @@ export function usePublicShare() {
 | 
				
			|||||||
      const zipName = `${share.name || t("shareManager.defaultShareName")}-selected.zip`;
 | 
					      const zipName = `${share.name || t("shareManager.defaultShareName")}-selected.zip`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      toast.promise(
 | 
					      toast.promise(
 | 
				
			||||||
        bulkDownloadShareWithQueue(allItems, share.files || [], share.folders || [], zipName, undefined, false).then(
 | 
					        bulkDownloadShareWithQueue(
 | 
				
			||||||
          () => {}
 | 
					          allItems,
 | 
				
			||||||
        ),
 | 
					          share.files || [],
 | 
				
			||||||
 | 
					          share.folders || [],
 | 
				
			||||||
 | 
					          zipName,
 | 
				
			||||||
 | 
					          undefined,
 | 
				
			||||||
 | 
					          false,
 | 
				
			||||||
 | 
					          password
 | 
				
			||||||
 | 
					        ).then(() => {}),
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          loading: t("shareManager.creatingZip"),
 | 
					          loading: t("shareManager.creatingZip"),
 | 
				
			||||||
          success: t("shareManager.zipDownloadSuccess"),
 | 
					          success: t("shareManager.zipDownloadSuccess"),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ export default function PublicSharePage() {
 | 
				
			|||||||
          {share && (
 | 
					          {share && (
 | 
				
			||||||
            <ShareDetails
 | 
					            <ShareDetails
 | 
				
			||||||
              share={share}
 | 
					              share={share}
 | 
				
			||||||
 | 
					              password={password}
 | 
				
			||||||
              onDownload={handleDownload}
 | 
					              onDownload={handleDownload}
 | 
				
			||||||
              onBulkDownload={handleBulkDownload}
 | 
					              onBulkDownload={handleBulkDownload}
 | 
				
			||||||
              onSelectedItemsBulkDownload={handleSelectedItemsBulkDownload}
 | 
					              onSelectedItemsBulkDownload={handleSelectedItemsBulkDownload}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,9 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ obje
 | 
				
			|||||||
  const { objectPath } = await params;
 | 
					  const { objectPath } = await params;
 | 
				
			||||||
  const cookieHeader = req.headers.get("cookie");
 | 
					  const cookieHeader = req.headers.get("cookie");
 | 
				
			||||||
  const objectName = objectPath.join("/");
 | 
					  const objectName = objectPath.join("/");
 | 
				
			||||||
  const url = `${API_BASE_URL}/files/${encodeURIComponent(objectName)}/download`;
 | 
					  const searchParams = req.nextUrl.searchParams;
 | 
				
			||||||
 | 
					  const queryString = searchParams.toString();
 | 
				
			||||||
 | 
					  const url = `${API_BASE_URL}/files/${encodeURIComponent(objectName)}/download${queryString ? `?${queryString}` : ""}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const apiRes = await fetch(url, {
 | 
					  const apiRes = await fetch(url, {
 | 
				
			||||||
    method: "GET",
 | 
					    method: "GET",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,18 @@ interface FilePreviewModalProps {
 | 
				
			|||||||
    description?: string;
 | 
					    description?: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  isReverseShare?: boolean;
 | 
					  isReverseShare?: boolean;
 | 
				
			||||||
 | 
					  sharePassword?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function FilePreviewModal({ isOpen, onClose, file, isReverseShare = false }: FilePreviewModalProps) {
 | 
					export function FilePreviewModal({
 | 
				
			||||||
 | 
					  isOpen,
 | 
				
			||||||
 | 
					  onClose,
 | 
				
			||||||
 | 
					  file,
 | 
				
			||||||
 | 
					  isReverseShare = false,
 | 
				
			||||||
 | 
					  sharePassword,
 | 
				
			||||||
 | 
					}: FilePreviewModalProps) {
 | 
				
			||||||
  const t = useTranslations();
 | 
					  const t = useTranslations();
 | 
				
			||||||
  const previewState = useFilePreview({ file, isOpen, isReverseShare });
 | 
					  const previewState = useFilePreview({ file, isOpen, isReverseShare, sharePassword });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Dialog open={isOpen} onOpenChange={onClose}>
 | 
					    <Dialog open={isOpen} onOpenChange={onClose}>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,9 +27,10 @@ interface UseFilePreviewProps {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
  isOpen: boolean;
 | 
					  isOpen: boolean;
 | 
				
			||||||
  isReverseShare?: boolean;
 | 
					  isReverseShare?: boolean;
 | 
				
			||||||
 | 
					  sharePassword?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useFilePreview({ file, isOpen, isReverseShare = false }: UseFilePreviewProps) {
 | 
					export function useFilePreview({ file, isOpen, isReverseShare = false, sharePassword }: UseFilePreviewProps) {
 | 
				
			||||||
  const t = useTranslations();
 | 
					  const t = useTranslations();
 | 
				
			||||||
  const [state, setState] = useState<FilePreviewState>({
 | 
					  const [state, setState] = useState<FilePreviewState>({
 | 
				
			||||||
    previewUrl: null,
 | 
					    previewUrl: null,
 | 
				
			||||||
@@ -181,7 +182,17 @@ export function useFilePreview({ file, isOpen, isReverseShare = false }: UseFile
 | 
				
			|||||||
        url = response.data.url;
 | 
					        url = response.data.url;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const encodedObjectName = encodeURIComponent(file.objectName);
 | 
					        const encodedObjectName = encodeURIComponent(file.objectName);
 | 
				
			||||||
        const response = await getDownloadUrl(encodedObjectName);
 | 
					        const params: Record<string, string> = {};
 | 
				
			||||||
 | 
					        if (sharePassword) params.password = sharePassword;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const response = await getDownloadUrl(
 | 
				
			||||||
 | 
					          encodedObjectName,
 | 
				
			||||||
 | 
					          Object.keys(params).length > 0
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                params: { ...params },
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : undefined
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        url = response.data.url;
 | 
					        url = response.data.url;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,6 +229,7 @@ export function useFilePreview({ file, isOpen, isReverseShare = false }: UseFile
 | 
				
			|||||||
    file.id,
 | 
					    file.id,
 | 
				
			||||||
    file.objectName,
 | 
					    file.objectName,
 | 
				
			||||||
    fileType,
 | 
					    fileType,
 | 
				
			||||||
 | 
					    sharePassword,
 | 
				
			||||||
    loadVideoPreview,
 | 
					    loadVideoPreview,
 | 
				
			||||||
    loadAudioPreview,
 | 
					    loadAudioPreview,
 | 
				
			||||||
    loadPdfPreview,
 | 
					    loadPdfPreview,
 | 
				
			||||||
@@ -236,13 +248,14 @@ export function useFilePreview({ file, isOpen, isReverseShare = false }: UseFile
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        await downloadFileWithQueue(file.objectName, file.name, {
 | 
					        await downloadFileWithQueue(file.objectName, file.name, {
 | 
				
			||||||
 | 
					          sharePassword,
 | 
				
			||||||
          onFail: () => toast.error(t("filePreview.downloadError")),
 | 
					          onFail: () => toast.error(t("filePreview.downloadError")),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      console.error("Download error:", error);
 | 
					      console.error("Download error:", error);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [isReverseShare, file.id, file.objectName, file.name, t]);
 | 
					  }, [isReverseShare, file.id, file.objectName, file.name, sharePassword, t]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const fileKey = isReverseShare ? file.id : file.objectName;
 | 
					    const fileKey = isReverseShare ? file.id : file.objectName;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ interface DownloadWithQueueOptions {
 | 
				
			|||||||
  useQueue?: boolean;
 | 
					  useQueue?: boolean;
 | 
				
			||||||
  silent?: boolean;
 | 
					  silent?: boolean;
 | 
				
			||||||
  showToasts?: boolean;
 | 
					  showToasts?: boolean;
 | 
				
			||||||
 | 
					  sharePassword?: string;
 | 
				
			||||||
  onStart?: (downloadId: string) => void;
 | 
					  onStart?: (downloadId: string) => void;
 | 
				
			||||||
  onComplete?: (downloadId: string) => void;
 | 
					  onComplete?: (downloadId: string) => void;
 | 
				
			||||||
  onFail?: (downloadId: string, error: string) => void;
 | 
					  onFail?: (downloadId: string, error: string) => void;
 | 
				
			||||||
@@ -89,7 +90,7 @@ export async function downloadFileWithQueue(
 | 
				
			|||||||
  fileName: string,
 | 
					  fileName: string,
 | 
				
			||||||
  options: DownloadWithQueueOptions = {}
 | 
					  options: DownloadWithQueueOptions = {}
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
  const { useQueue = true, silent = false, showToasts = true } = options;
 | 
					  const { useQueue = true, silent = false, showToasts = true, sharePassword } = options;
 | 
				
			||||||
  const downloadId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
 | 
					  const downloadId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -98,7 +99,18 @@ export async function downloadFileWithQueue(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const encodedObjectName = encodeURIComponent(objectName);
 | 
					    const encodedObjectName = encodeURIComponent(objectName);
 | 
				
			||||||
    const response = await getDownloadUrl(encodedObjectName);
 | 
					
 | 
				
			||||||
 | 
					    const params: Record<string, string> = {};
 | 
				
			||||||
 | 
					    if (sharePassword) params.password = sharePassword;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const response = await getDownloadUrl(
 | 
				
			||||||
 | 
					      encodedObjectName,
 | 
				
			||||||
 | 
					      Object.keys(params).length > 0
 | 
				
			||||||
 | 
					        ? {
 | 
				
			||||||
 | 
					            params: { ...params },
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        : undefined
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.status === 202 && useQueue) {
 | 
					    if (response.status === 202 && useQueue) {
 | 
				
			||||||
      if (!silent && showToasts) {
 | 
					      if (!silent && showToasts) {
 | 
				
			||||||
@@ -181,7 +193,8 @@ export async function downloadFileAsBlobWithQueue(
 | 
				
			|||||||
  objectName: string,
 | 
					  objectName: string,
 | 
				
			||||||
  fileName: string,
 | 
					  fileName: string,
 | 
				
			||||||
  isReverseShare: boolean = false,
 | 
					  isReverseShare: boolean = false,
 | 
				
			||||||
  fileId?: string
 | 
					  fileId?: string,
 | 
				
			||||||
 | 
					  sharePassword?: string
 | 
				
			||||||
): Promise<Blob> {
 | 
					): Promise<Blob> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    let downloadUrl: string;
 | 
					    let downloadUrl: string;
 | 
				
			||||||
@@ -196,7 +209,18 @@ export async function downloadFileAsBlobWithQueue(
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      const encodedObjectName = encodeURIComponent(objectName);
 | 
					      const encodedObjectName = encodeURIComponent(objectName);
 | 
				
			||||||
      const response = await getDownloadUrl(encodedObjectName);
 | 
					
 | 
				
			||||||
 | 
					      const params: Record<string, string> = {};
 | 
				
			||||||
 | 
					      if (sharePassword) params.password = sharePassword;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const response = await getDownloadUrl(
 | 
				
			||||||
 | 
					        encodedObjectName,
 | 
				
			||||||
 | 
					        Object.keys(params).length > 0
 | 
				
			||||||
 | 
					          ? {
 | 
				
			||||||
 | 
					              params: { ...params },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          : undefined
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (response.status === 202) {
 | 
					      if (response.status === 202) {
 | 
				
			||||||
        downloadUrl = await waitForDownloadReady(objectName, fileName);
 | 
					        downloadUrl = await waitForDownloadReady(objectName, fileName);
 | 
				
			||||||
@@ -345,7 +369,7 @@ export async function downloadShareFolderWithQueue(
 | 
				
			|||||||
  shareFolders: any[],
 | 
					  shareFolders: any[],
 | 
				
			||||||
  options: DownloadWithQueueOptions = {}
 | 
					  options: DownloadWithQueueOptions = {}
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
  const { silent = false, showToasts = true } = options;
 | 
					  const { silent = false, showToasts = true, sharePassword } = options;
 | 
				
			||||||
  const downloadId = `share-folder-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
 | 
					  const downloadId = `share-folder-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -373,7 +397,7 @@ export async function downloadShareFolderWithQueue(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (const file of folderFiles) {
 | 
					    for (const file of folderFiles) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name);
 | 
					        const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name, false, undefined, sharePassword);
 | 
				
			||||||
        zip.file(file.zipPath, blob);
 | 
					        zip.file(file.zipPath, blob);
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
        console.error(`Error downloading file ${file.name}:`, error);
 | 
					        console.error(`Error downloading file ${file.name}:`, error);
 | 
				
			||||||
@@ -515,7 +539,8 @@ export async function bulkDownloadShareWithQueue(
 | 
				
			|||||||
  shareFolders: any[],
 | 
					  shareFolders: any[],
 | 
				
			||||||
  zipName: string,
 | 
					  zipName: string,
 | 
				
			||||||
  onProgress?: (current: number, total: number) => void,
 | 
					  onProgress?: (current: number, total: number) => void,
 | 
				
			||||||
  wrapInFolder?: boolean
 | 
					  wrapInFolder?: boolean,
 | 
				
			||||||
 | 
					  sharePassword?: string
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const JSZip = (await import("jszip")).default;
 | 
					    const JSZip = (await import("jszip")).default;
 | 
				
			||||||
@@ -562,7 +587,7 @@ export async function bulkDownloadShareWithQueue(
 | 
				
			|||||||
    for (let i = 0; i < allFilesToDownload.length; i++) {
 | 
					    for (let i = 0; i < allFilesToDownload.length; i++) {
 | 
				
			||||||
      const file = allFilesToDownload[i];
 | 
					      const file = allFilesToDownload[i];
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name);
 | 
					        const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name, false, undefined, sharePassword);
 | 
				
			||||||
        zip.file(file.zipPath, blob);
 | 
					        zip.file(file.zipPath, blob);
 | 
				
			||||||
        onProgress?.(i + 1, allFilesToDownload.length);
 | 
					        onProgress?.(i + 1, allFilesToDownload.length);
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "palmr-monorepo",
 | 
					  "name": "palmr-monorepo",
 | 
				
			||||||
  "version": "3.2.1-beta",
 | 
					  "version": "3.2.2-beta",
 | 
				
			||||||
  "description": "Palmr monorepo with Husky configuration",
 | 
					  "description": "Palmr monorepo with Husky configuration",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "packageManager": "pnpm@10.6.0",
 | 
					  "packageManager": "pnpm@10.6.0",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user