diff --git a/apps/app/package.json b/apps/app/package.json index 5054b762ba..c80be849be 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -8,7 +8,7 @@ "@ai-sdk/react": "^1.2.9", "@aws-sdk/client-s3": "^3.806.0", "@aws-sdk/client-sts": "^3.808.0", - "@aws-sdk/s3-request-presigner": "^3.806.0", + "@aws-sdk/s3-request-presigner": "^3.832.0", "@azure/core-rest-pipeline": "^1.21.0", "@browserbasehq/sdk": "^2.5.0", "@calcom/atoms": "^1.0.102-framer", diff --git a/apps/portal/package.json b/apps/portal/package.json index c00c502184..45ce5c2045 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -2,14 +2,17 @@ "name": "@comp/portal", "version": "0.1.0", "dependencies": { + "@aws-sdk/s3-request-presigner": "^3.832.0", "@comp/db": "workspace:*", "@comp/ui": "workspace:*", "@react-email/components": "^0.0.41", "@react-email/render": "^1.1.2", "@t3-oss/env-nextjs": "^0.13.8", + "@types/jszip": "^3.4.1", "archiver": "^7.0.1", "better-auth": "^1.2.8", "class-variance-authority": "^0.7.1", + "jszip": "^3.10.1", "next": "15.4.0-canary.85", "react-email": "^4.0.15" }, diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx b/apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx index d6c0ccfa0b..f854f38554 100644 --- a/apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx +++ b/apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx @@ -5,7 +5,9 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c import { Button } from '@comp/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@comp/ui/card'; import { cn } from '@comp/ui/cn'; -import { CheckCircle2, Circle, Download, XCircle } from 'lucide-react'; +import { Progress } from '@comp/ui/progress'; +import JSZip from 'jszip'; +import { CheckCircle2, Circle, Download, Loader2, XCircle } from 'lucide-react'; import Image from 'next/image'; import { useState } from 'react'; import { toast } from 'sonner'; @@ -17,12 +19,15 @@ interface DeviceAgentAccordionItemProps { fleetPolicies?: FleetPolicy[]; } +type DownloadStatus = 'idle' | 'preparing' | 'downloading' | 'creating-zip' | 'complete'; + export function DeviceAgentAccordionItem({ member, host, fleetPolicies = [], }: DeviceAgentAccordionItemProps) { - const [isDownloading, setIsDownloading] = useState(false); + const [downloadStatus, setDownloadStatus] = useState('idle'); + const [downloadProgress, setDownloadProgress] = useState(0); const hasInstalledAgent = host !== null; const allPoliciesPass = @@ -30,8 +35,11 @@ export function DeviceAgentAccordionItem({ const isCompleted = hasInstalledAgent && allPoliciesPass; const handleDownload = async () => { - setIsDownloading(true); + setDownloadStatus('preparing'); + setDownloadProgress(0); + try { + // Step 1: Get download URL and script content from the API const response = await fetch('/api/download-agent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -43,11 +51,105 @@ export function DeviceAgentAccordionItem({ if (!response.ok) { const errorText = await response.text(); - throw new Error(errorText || 'Failed to download agent.'); + throw new Error(errorText || 'Failed to get download information.'); + } + + const { scriptContent, scriptFilename, packageDownloadUrl, packageFilename } = + await response.json(); + + // Step 2: Download the package file + setDownloadStatus('downloading'); + setDownloadProgress(10); + + const packageResponse = await fetch(packageDownloadUrl); + + if (!packageResponse.ok) { + throw new Error('Failed to download agent package.'); + } + + // Get the content length for progress tracking + const contentLength = packageResponse.headers.get('content-length'); + const total = contentLength ? parseInt(contentLength, 10) : 0; + + // Read the response with progress tracking + const reader = packageResponse.body?.getReader(); + if (!reader) { + throw new Error('Failed to read package data.'); } - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); + const chunks: Uint8Array[] = []; + let receivedLength = 0; + + while (true) { + const { done, value } = await reader.read(); + + if (done) break; + + chunks.push(value); + receivedLength += value.length; + + if (total > 0) { + // Update progress (10-70% range for download) + const downloadPercent = (receivedLength / total) * 60 + 10; + setDownloadProgress(Math.round(downloadPercent)); + } + } + + // Combine chunks into a single Uint8Array + const chunksAll = new Uint8Array(receivedLength); + let position = 0; + for (const chunk of chunks) { + chunksAll.set(chunk, position); + position += chunk.length; + } + + const packageBlob = new Blob([chunksAll]); + + // Step 3: Create zip file using JSZip + setDownloadStatus('creating-zip'); + setDownloadProgress(75); + + const zip = new JSZip(); + + // Add the script file + const scriptBlob = new Blob([scriptContent], { type: 'text/plain' }); + zip.file(scriptFilename, scriptBlob); + + // Add the package file + zip.file(packageFilename, packageBlob); + + // Add README + const readmeContent = `Comp AI Device Agent Installation Instructions + +1. Extract this zip file to a folder on your computer +2. Run the "Install Me First" file first +3. Then run the Fleet installer package + +For macOS: +- Run: ./${scriptFilename} +- Then open the .pkg file + +For Windows: +- Run: ${scriptFilename} +- Then run the .msi installer + +If you have any issues, please contact your IT administrator.`; + + zip.file('README.txt', readmeContent); + + setDownloadProgress(85); + + // Step 4: Generate and download the zip + const zipBlob = await zip.generateAsync({ + type: 'blob', + compression: 'DEFLATE', + compressionOptions: { level: 9 }, + }); + + setDownloadProgress(100); + + // Create download link + const url = window.URL.createObjectURL(zipBlob); const a = document.createElement('a'); a.href = url; a.download = 'compai-device-agent.zip'; @@ -55,11 +157,60 @@ export function DeviceAgentAccordionItem({ a.click(); a.remove(); window.URL.revokeObjectURL(url); + + setDownloadStatus('complete'); + toast.success('Download completed successfully!'); + + // Reset after a delay + setTimeout(() => { + setDownloadStatus('idle'); + setDownloadProgress(0); + }, 3000); } catch (error) { console.error(error); toast.error(error instanceof Error ? error.message : 'An unknown error occurred.'); - } finally { - setIsDownloading(false); + setDownloadStatus('idle'); + setDownloadProgress(0); + } + }; + + const getButtonContent = () => { + switch (downloadStatus) { + case 'preparing': + return ( + <> + + Preparing download... + + ); + case 'downloading': + return ( + <> + + Downloading package... + + ); + case 'creating-zip': + return ( + <> + + Creating installer... + + ); + case 'complete': + return ( + <> + + Download complete! + + ); + default: + return ( + <> + + Download Agent + + ); } }; @@ -101,12 +252,14 @@ export function DeviceAgentAccordionItem({ size="sm" variant="default" onClick={handleDownload} - disabled={isDownloading || hasInstalledAgent} + disabled={downloadStatus !== 'idle' || hasInstalledAgent} className="gap-2 mt-2" > - - {isDownloading ? 'Downloading...' : 'Download Agent'} + {getButtonContent()} + {downloadStatus !== 'idle' && downloadStatus !== 'complete' && ( + + )}
  • Run the "Install Me First" file diff --git a/apps/portal/src/app/api/download-agent/route.ts b/apps/portal/src/app/api/download-agent/route.ts index 225384c179..fc1ca18fef 100644 --- a/apps/portal/src/app/api/download-agent/route.ts +++ b/apps/portal/src/app/api/download-agent/route.ts @@ -1,12 +1,14 @@ import { auth } from '@/app/lib/auth'; import { logger } from '@/utils/logger'; +import { BUCKET_NAME, getPresignedDownloadUrl } from '@/utils/s3'; import { type NextRequest, NextResponse } from 'next/server'; -import { promises as fs } from 'node:fs'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { createAgentArchive } from './archive'; import { createFleetLabel } from './fleet-label'; -import { generateMacScript, generateWindowsScript } from './scripts'; +import { + generateMacScript, + generateWindowsScript, + getPackageFilename, + getScriptFilename, +} from './scripts'; import type { DownloadAgentRequest, SupportedOS } from './types'; import { detectOSFromUserAgent, validateMemberAndOrg } from './utils'; @@ -44,6 +46,7 @@ export async function POST(req: NextRequest) { // Check environment configuration const fleetDevicePathMac = process.env.FLEET_DEVICE_PATH_MAC; const fleetDevicePathWindows = process.env.FLEET_DEVICE_PATH_WINDOWS; + const fleetBucketName = process.env.FLEET_AGENT_BUCKET_NAME; if (!fleetDevicePathMac || !fleetDevicePathWindows) { logger( @@ -57,6 +60,12 @@ export async function POST(req: NextRequest) { ); } + if (!fleetBucketName || !BUCKET_NAME) { + return new NextResponse('Server configuration error: S3 bucket names are missing.', { + status: 500, + }); + } + // Validate member and organization const member = await validateMemberAndOrg(session.user.id, orgId); if (!member) { @@ -70,18 +79,7 @@ export async function POST(req: NextRequest) { ? generateMacScript({ orgId, employeeId, fleetDevicePath }) : generateWindowsScript({ orgId, employeeId, fleetDevicePath }); - // Create temporary directory - const tempDir = path.join(tmpdir(), `compai-agent-${Date.now()}`); - await fs.mkdir(tempDir, { recursive: true }); - try { - // Create the archive - const stream = await createAgentArchive({ - os: os as SupportedOS, - script, - tempDir, - }); - // Create Fleet label await createFleetLabel({ employeeId, @@ -91,20 +89,26 @@ export async function POST(req: NextRequest) { fleetDevicePathWindows, }); - const filename = `compai-device-agent-${os}.zip`; + // Get script filename + const scriptFilename = getScriptFilename(os); - return new NextResponse(stream as unknown as ReadableStream, { - headers: { - 'Content-Type': 'application/zip', - 'Content-Disposition': `attachment; filename="${filename}"`, - }, + // Get presigned URL for the Fleet agent package + const packageFilename = getPackageFilename(os); + const packageKey = `${os}/fleet-osquery.${os === 'macos' ? 'pkg' : 'msi'}`; + const packageDownloadUrl = await getPresignedDownloadUrl({ + bucketName: fleetBucketName, + key: packageKey, + expiresIn: 3600, // 1 hour + }); + + return NextResponse.json({ + scriptContent: script, + scriptFilename, + packageDownloadUrl, + packageFilename, }); - } finally { - // Clean up temp directory - try { - await fs.rm(tempDir, { recursive: true, force: true }); - } catch (cleanupError) { - logger('Failed to clean up temp directory', { error: cleanupError, tempDir }); - } + } catch (error) { + logger('Error generating presigned URLs', { error }); + return new NextResponse('Failed to generate download URLs', { status: 500 }); } } diff --git a/apps/portal/src/utils/s3.ts b/apps/portal/src/utils/s3.ts index b73f2662e3..fe3498684f 100644 --- a/apps/portal/src/utils/s3.ts +++ b/apps/portal/src/utils/s3.ts @@ -1,4 +1,5 @@ -import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; const AWS_REGION = process.env.AWS_REGION; const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; @@ -138,3 +139,46 @@ export async function getFleetAgent({ os }: { os: 'macos' | 'windows' }) { const response = await s3Client.send(getFleetAgentCommand); return response.Body; } + +/** + * Generates a presigned URL for downloading a file from S3 + */ +export async function getPresignedDownloadUrl({ + bucketName, + key, + expiresIn = 3600, // 1 hour default +}: { + bucketName: string; + key: string; + expiresIn?: number; +}): Promise { + const command = new GetObjectCommand({ + Bucket: bucketName, + Key: key, + }); + + return await getSignedUrl(s3Client, command, { expiresIn }); +} + +/** + * Generates a presigned URL for uploading a file to S3 + */ +export async function getPresignedUploadUrl({ + bucketName, + key, + contentType, + expiresIn = 3600, // 1 hour default +}: { + bucketName: string; + key: string; + contentType?: string; + expiresIn?: number; +}): Promise { + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: key, + ContentType: contentType, + }); + + return await getSignedUrl(s3Client, command, { expiresIn }); +} diff --git a/bun.lock b/bun.lock index efae11d292..490436d1a9 100644 --- a/bun.lock +++ b/bun.lock @@ -183,14 +183,17 @@ "name": "@comp/portal", "version": "0.1.0", "dependencies": { + "@aws-sdk/s3-request-presigner": "^3.832.0", "@comp/db": "workspace:*", "@comp/ui": "workspace:*", "@react-email/components": "^0.0.41", "@react-email/render": "^1.1.2", "@t3-oss/env-nextjs": "^0.13.8", + "@types/jszip": "^3.4.1", "archiver": "^7.0.1", "better-auth": "^1.2.8", "class-variance-authority": "^0.7.1", + "jszip": "^3.10.1", "next": "15.4.0-canary.85", "react-email": "^4.0.15", }, @@ -1677,6 +1680,8 @@ "@types/jsonwebtoken": ["@types/jsonwebtoken@8.5.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg=="], + "@types/jszip": ["@types/jszip@3.4.1", "", { "dependencies": { "jszip": "*" } }, "sha512-TezXjmf3lj+zQ651r6hPqvSScqBLvyPI9FxdXBqpEwBijNGQ2NXpaFW/7joGzveYkKQUil7iiDHLo6LV71Pc0A=="], + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], "@types/lodash": ["@types/lodash@4.17.18", "", {}, "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g=="], @@ -2667,6 +2672,8 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-from-esm": ["import-from-esm@2.0.0", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g=="], @@ -2861,6 +2868,8 @@ "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], @@ -2885,6 +2894,8 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -3283,6 +3294,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -3675,6 +3688,8 @@ "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], @@ -4401,6 +4416,8 @@ "jsondiffpatch/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], @@ -5163,6 +5180,10 @@ "geist/next/sharp": ["sharp@0.34.2", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.2", "@img/sharp-darwin-x64": "0.34.2", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm64": "0.34.2", "@img/sharp-linux-s390x": "0.34.2", "@img/sharp-linux-x64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.2", "@img/sharp-linuxmusl-x64": "0.34.2", "@img/sharp-wasm32": "0.34.2", "@img/sharp-win32-arm64": "0.34.2", "@img/sharp-win32-ia32": "0.34.2", "@img/sharp-win32-x64": "0.34.2" } }, "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg=="], + "jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], diff --git a/yarn.lock b/yarn.lock index 2674bb852d..c2788af917 100644 --- a/yarn.lock +++ b/yarn.lock @@ -636,7 +636,7 @@ "@smithy/util-middleware" "^4.0.4" tslib "^2.6.2" -"@aws-sdk/s3-request-presigner@^3.806.0": +"@aws-sdk/s3-request-presigner@^3.806.0", "@aws-sdk/s3-request-presigner@^3.832.0": version "3.832.0" resolved "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.832.0.tgz" integrity sha512-zXuwfaAYu99LUF7/6iBr3UlKCMaMImBwfmLXJQlvtE3ebrERXQuISME9Vjd2oG+hJ6XcX6RJqkeIvZBytMzvRw== @@ -1389,6 +1389,8 @@ react-dom "^19.1.0" tailwindcss "^4.1.8" typescript "^5.8.3" + dependencies: + "@aws-sdk/s3-request-presigner" "^3.832.0" dependencies: "@comp/db" "workspace:*" "@comp/ui" "workspace:*" @@ -1396,9 +1398,11 @@ "@react-email/components" "^0.0.41" "@react-email/render" "^1.1.2" "@t3-oss/env-nextjs" "^0.13.8" + "@types/jszip" "^3.4.1" archiver "^7.0.1" better-auth "^1.2.8" class-variance-authority "^0.7.1" + jszip "^3.10.1" next "15.4.0-canary.85" react-email "^4.0.15" @@ -5974,6 +5978,13 @@ dependencies: "@types/node" "*" +"@types/jszip@^3.4.1": + version "3.4.1" + resolved "https://registry.npmjs.org/@types/jszip/-/jszip-3.4.1.tgz" + integrity sha512-TezXjmf3lj+zQ651r6hPqvSScqBLvyPI9FxdXBqpEwBijNGQ2NXpaFW/7joGzveYkKQUil7iiDHLo6LV71Pc0A== + dependencies: + jszip "*" + "@types/linkify-it@^5": version "5.0.0" resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz" @@ -10220,6 +10231,11 @@ ignore-walk@^7.0.0: dependencies: minimatch "^9.0.0" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" @@ -10979,6 +10995,16 @@ jsprim@^1.2.2: object.assign "^4.1.4" object.values "^1.1.6" +jszip@*, jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + just-diff@^6.0.0: version "6.0.2" resolved "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz" @@ -11178,6 +11204,13 @@ libnpmversion@^7.0.0: proc-log "^5.0.0" semver "^7.3.7" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lightningcss@1.30.1: version "1.30.1" resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz" @@ -13147,6 +13180,11 @@ pacote@^20.0.0: ssri "^12.0.0" tar "^6.1.11" +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -14914,6 +14952,11 @@ set-proto@^1.0.0: es-errors "^1.3.0" es-object-atoms "^1.0.0" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"