@burdenoff/microfe-export 2026.1023.2 → 2026.1107.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/export/hooks/useExportQueries.d.ts +4 -2
- package/dist/export/hooks/useExportQueries.d.ts.map +1 -1
- package/dist/export/hooks/useExportQueries.js +176 -164
- package/dist/export/hooks/useExportQueries.js.map +1 -1
- package/dist/export/pages/CreateExportPage.d.ts.map +1 -1
- package/dist/export/pages/CreateExportPage.js +190 -190
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportDetailPage.d.ts.map +1 -1
- package/dist/export/pages/ExportDetailPage.js +174 -173
- package/dist/export/pages/ExportDetailPage.js.map +1 -1
- package/dist/export/providers/ExportProvider.d.ts.map +1 -1
- package/dist/export/providers/ExportProvider.js +68 -63
- package/dist/export/providers/ExportProvider.js.map +1 -1
- package/dist/export/types/index.d.ts +3 -0
- package/dist/export/types/index.d.ts.map +1 -1
- package/dist/export/utils/exportAuthority.d.ts +18 -0
- package/dist/export/utils/exportAuthority.d.ts.map +1 -0
- package/dist/export/utils/exportAuthority.js +52 -0
- package/dist/export/utils/exportAuthority.js.map +1 -0
- package/dist/generated/wspace-operations.d.ts +70 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-operations.js +41 -6
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport {\n GlassCard,\n CTAOverflowMenu,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n const tr = useTr();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob();\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // Build a single primary CTA + overflow menu for the header. Download (the\n // first available part) is the primary action when the export is ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const firstDownloadablePart = liveJob?.parts.find((p) => p.downloadUrl);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n {tr(\"export.detail.loading\", \"Loading export details…\")}\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label={tr(\"export.detail.actions.back\", \"Go back\")}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">\n {tr(\"export.detail.notFound.title\", \"Export Not Found\")}\n </h1>\n </div>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr(\"export.detail.notFound.heading\", \"Export not found\")}\n description={tr(\n \"export.detail.notFound.description\",\n \"The export job you are looking for does not exist or has been deleted.\",\n )}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n {tr(\"export.detail.actions.backToDashboard\", \"Back to Dashboard\")}\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = firstDownloadablePart\n ? {\n label: tr(\"export.detail.actions.download\", \"Download\"),\n icon: <Download size={16} />,\n onSelect: () => {\n if (firstDownloadablePart.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: tr(\"export.detail.actions.retry\", \"Retry Export\"),\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && firstDownloadablePart) {\n overflowActions.push({\n label: tr(\"export.detail.actions.retry\", \"Retry Export\"),\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: tr(\"export.detail.actions.cancel\", \"Cancel Export\"),\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: tr(\"export.detail.actions.delete\", \"Delete Export\"),\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n // Next-step guidance — concrete actions wired to real handlers/links, tuned\n // to the job's current state so a new user knows what to do from here.\n const nextSteps: NextStep[] = [\n {\n id: \"download\",\n label: tr(\n \"export.detail.nextSteps.download.label\",\n \"Download your export\",\n ),\n description: tr(\n \"export.detail.nextSteps.download.description\",\n \"Grab the generated files below before their download links expire.\",\n ),\n icon: <Download size={16} />,\n onClick: () => {\n if (firstDownloadablePart?.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n done: isCompleted,\n },\n ...(isRetryable\n ? [\n {\n id: \"retry\",\n label: tr(\n \"export.detail.nextSteps.retry.label\",\n \"Retry this export\",\n ),\n description: tr(\n \"export.detail.nextSteps.retry.description\",\n \"This export didn't finish — start a fresh run with the same settings.\",\n ),\n icon: <RotateCcw size={16} />,\n onClick: () => {\n void handleRetry();\n },\n } as NextStep,\n ]\n : []),\n {\n id: \"history\",\n label: tr(\"export.detail.nextSteps.history.label\", \"Back to History\"),\n description: tr(\n \"export.detail.nextSteps.history.description\",\n \"See all your exports and track other jobs in progress.\",\n ),\n icon: <Clock size={16} />,\n onClick: () => navigate?.(basePath),\n },\n ];\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label={tr(\"export.detail.actions.back\", \"Go back\")}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr(\n \"export.detail.progress.entitiesProcessed\",\n \"{{processed}} / {{total}} entities processed\",\n {\n processed: liveJob.processedEntities.toLocaleString(),\n total: liveJob.totalEntities.toLocaleString(),\n },\n )}\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.detail.error.title\", \"Export Failed\")}\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: tr(\"export.detail.info.format\", \"Format\"),\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: tr(\"export.detail.info.archive\", \"Archive\"),\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: tr(\"export.detail.info.totalSize\", \"Total Size\"),\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: tr(\"export.detail.info.created\", \"Created\"),\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Next-step guidance for this export (dismissable, per-job) */}\n <NextSteps\n storageKey={`export-detail-${liveJob.id}`}\n title={tr(\"export.detail.nextSteps.title\", \"What's next\")}\n steps={nextSteps}\n />\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title={tr(\"export.detail.parts.title\", \"Export Parts\")}\n description={tr(\n \"export.detail.parts.description\",\n \"{{count}} file(s) generated\",\n { count: liveJob.parts.length },\n )}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} ·{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · ${tr(\n \"export.detail.parts.expires\",\n \"expires {{date}}\",\n {\n date: new Date(\n part.downloadExpiresAt,\n ).toLocaleDateString(),\n },\n )}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n {tr(\"export.detail.parts.download\", \"Download\")}\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n {tr(\"export.detail.parts.pending\", \"Pending\")}\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n {tr(\"export.detail.complete.title\", \"Export Complete\")}\n </h3>\n <p className=\"text-sm text-status-success-text\">\n {tr(\n \"export.detail.complete.noParts\",\n \"Your export has completed but no download parts are available yet.\",\n )}\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\"\n ? tr(\"export.detail.confirm.cancel.title\", \"Cancel export?\")\n : tr(\"export.detail.confirm.delete.title\", \"Delete export?\")}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? tr(\n \"export.detail.confirm.cancel.description\",\n \"This stops the running export job. You can retry it later if needed.\",\n )\n : tr(\n \"export.detail.confirm.delete.description\",\n \"This removes the export job from history. This action cannot be undone.\",\n )}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.detail.confirm.keep\", \"Keep Export\")}\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\"\n ? tr(\"export.detail.actions.cancel\", \"Cancel Export\")\n : tr(\"export.detail.actions.delete\", \"Delete Export\")}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { exportArchiveDownload } from \"../utils/exportAuthority\";\nimport { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport {\n GlassCard,\n CTAOverflowMenu,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n const tr = useTr();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob(\n job?.exportScope ?? \"INDIVIDUAL\",\n );\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // The primary download is the complete durable archive when ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const archiveDownloadUrl = exportArchiveDownload(liveJob);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n {tr(\"export.detail.loading\", \"Loading export details…\")}\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label={tr(\"export.detail.actions.back\", \"Go back\")}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">\n {tr(\"export.detail.notFound.title\", \"Export Not Found\")}\n </h1>\n </div>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr(\"export.detail.notFound.heading\", \"Export not found\")}\n description={tr(\n \"export.detail.notFound.description\",\n \"The export job you are looking for does not exist or has been deleted.\",\n )}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n {tr(\"export.detail.actions.backToDashboard\", \"Back to Dashboard\")}\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = archiveDownloadUrl\n ? {\n label: tr(\"export.detail.actions.downloadArchive\", \"Download Archive\"),\n icon: <Download size={16} />,\n onSelect: () => {\n if (archiveDownloadUrl) {\n window.location.assign(archiveDownloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: tr(\"export.detail.actions.retry\", \"Retry Export\"),\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && archiveDownloadUrl) {\n overflowActions.push({\n label: tr(\"export.detail.actions.retry\", \"Retry Export\"),\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: tr(\"export.detail.actions.cancel\", \"Cancel Export\"),\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: tr(\"export.detail.actions.delete\", \"Delete Export\"),\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n // Next-step guidance — concrete actions wired to real handlers/links, tuned\n // to the job's current state so a new user knows what to do from here.\n const nextSteps: NextStep[] = [\n {\n id: \"download\",\n label: tr(\n \"export.detail.nextSteps.download.label\",\n \"Download your export\",\n ),\n description: tr(\n \"export.detail.nextSteps.download.description\",\n \"Grab the generated files below before their download links expire.\",\n ),\n icon: <Download size={16} />,\n onClick: () => {\n if (archiveDownloadUrl) {\n window.location.assign(archiveDownloadUrl);\n }\n },\n done: isCompleted,\n },\n ...(isRetryable\n ? [\n {\n id: \"retry\",\n label: tr(\n \"export.detail.nextSteps.retry.label\",\n \"Retry this export\",\n ),\n description: tr(\n \"export.detail.nextSteps.retry.description\",\n \"This export didn't finish — start a fresh run with the same settings.\",\n ),\n icon: <RotateCcw size={16} />,\n onClick: () => {\n void handleRetry();\n },\n } as NextStep,\n ]\n : []),\n {\n id: \"history\",\n label: tr(\"export.detail.nextSteps.history.label\", \"Back to History\"),\n description: tr(\n \"export.detail.nextSteps.history.description\",\n \"See all your exports and track other jobs in progress.\",\n ),\n icon: <Clock size={16} />,\n onClick: () => navigate?.(basePath),\n },\n ];\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label={tr(\"export.detail.actions.back\", \"Go back\")}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr(\n \"export.detail.progress.entitiesProcessed\",\n \"{{processed}} / {{total}} entities processed\",\n {\n processed: liveJob.processedEntities.toLocaleString(),\n total: liveJob.totalEntities.toLocaleString(),\n },\n )}\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.detail.error.title\", \"Export Failed\")}\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: tr(\"export.detail.info.format\", \"Format\"),\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: tr(\"export.detail.info.archive\", \"Archive\"),\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: tr(\"export.detail.info.totalSize\", \"Total Size\"),\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: tr(\"export.detail.info.created\", \"Created\"),\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Next-step guidance for this export (dismissable, per-job) */}\n <NextSteps\n storageKey={`export-detail-${liveJob.id}`}\n title={tr(\"export.detail.nextSteps.title\", \"What's next\")}\n steps={nextSteps}\n />\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title={tr(\"export.detail.parts.title\", \"Export Parts\")}\n description={tr(\n \"export.detail.parts.description\",\n \"{{count}} file(s) generated\",\n { count: liveJob.parts.length },\n )}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} ·{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · ${tr(\n \"export.detail.parts.expires\",\n \"expires {{date}}\",\n {\n date: new Date(\n part.downloadExpiresAt,\n ).toLocaleDateString(),\n },\n )}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n {tr(\"export.detail.parts.download\", \"Download\")}\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n {tr(\"export.detail.parts.pending\", \"Pending\")}\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n {tr(\"export.detail.complete.title\", \"Export Complete\")}\n </h3>\n <p className=\"text-sm text-status-success-text\">\n {tr(\n \"export.detail.complete.noParts\",\n \"Your export has completed but no download parts are available yet.\",\n )}\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\"\n ? tr(\"export.detail.confirm.cancel.title\", \"Cancel export?\")\n : tr(\"export.detail.confirm.delete.title\", \"Delete export?\")}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? tr(\n \"export.detail.confirm.cancel.description\",\n \"This stops the running export job. You can retry it later if needed.\",\n )\n : tr(\n \"export.detail.confirm.delete.description\",\n \"This removes the export job from history. This action cannot be undone.\",\n )}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.detail.confirm.keep\", \"Keep Export\")}\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\"\n ? tr(\"export.detail.actions.cancel\", \"Cancel Export\")\n : tr(\"export.detail.actions.delete\", \"Delete Export\")}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportProvider.d.ts","sourceRoot":"","sources":["../../../src/export/providers/ExportProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ExportProvider.d.ts","sourceRoot":"","sources":["../../../src/export/providers/ExportProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AA6BnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAsChD;;GAEG;AACH,eAAO,MAAM,SAAS,uBAMrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAkMjE,CAAC"}
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
import { getMainDefinition as e } from "../../node_modules/@apollo/client/utilities/internal/getMainDefinition.js";
|
|
2
2
|
import { GraphQLWsLink as t } from "../../node_modules/@apollo/client/link/subscriptions/index.js";
|
|
3
3
|
import { createClient as n } from "../../node_modules/graphql-ws/dist/client.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
4
|
+
import { exportGatewayEndpoints as r, exportGatewayHeaders as i } from "../utils/exportAuthority.js";
|
|
5
|
+
import { createContext as a, useContext as o, useMemo as s, useRef as c } from "react";
|
|
6
|
+
import { MemoryRouter as l } from "react-router";
|
|
7
|
+
import { MutationCache as u, QueryClient as d, QueryClientProvider as f } from "@tanstack/react-query";
|
|
8
|
+
import { ApolloClient as p, HttpLink as m, InMemoryCache as h, Observable as g, from as _, split as v } from "@apollo/client";
|
|
9
|
+
import { onError as y } from "@apollo/client/link/error";
|
|
10
|
+
import { ApolloProvider as b } from "@apollo/client/react";
|
|
11
|
+
import { isAuthNetworkError as x, redirectToLogin as S } from "@burdenoff/fe-libs/shared/utils";
|
|
12
|
+
import { jsx as C } from "react/jsx-runtime";
|
|
12
13
|
//#region src/export/providers/ExportProvider.tsx
|
|
13
|
-
function
|
|
14
|
+
function w(e = []) {
|
|
14
15
|
return e.some((e) => {
|
|
15
16
|
let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
|
|
16
17
|
return t === "UNAUTHENTICATED" || n === "TOKEN_EXPIRED" || r.includes("token_expired") || r.includes("token expired") || r.includes("session expired") || r.includes("unauthorized") || r.includes("unauthenticated") || r.includes("authentication required") || r.includes("workspace context missing");
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
|
-
var
|
|
20
|
-
let e =
|
|
20
|
+
var T = a(null), E = () => {
|
|
21
|
+
let e = o(T);
|
|
21
22
|
if (!e) throw Error("useExport must be used within ExportProvider");
|
|
22
23
|
return e;
|
|
23
|
-
},
|
|
24
|
-
let
|
|
25
|
-
basePath:
|
|
26
|
-
navigate:
|
|
27
|
-
currentUser:
|
|
28
|
-
workspaceId:
|
|
29
|
-
tenantId:
|
|
30
|
-
organizationId:
|
|
31
|
-
apiGatewayUrl:
|
|
32
|
-
authToken:
|
|
33
|
-
workspaceToken:
|
|
34
|
-
eventBus:
|
|
35
|
-
getWorkspaceToken:
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
let
|
|
39
|
-
let e = new
|
|
40
|
-
mutationCache: new
|
|
24
|
+
}, D = ({ children: a, basePath: o, navigate: E, currentUser: D, workspaceId: O, tenantId: k, organizationId: A, apiGatewayUrl: j, authToken: M, workspaceToken: N, eventBus: P, getWorkspaceToken: F }) => {
|
|
25
|
+
let I = {
|
|
26
|
+
basePath: o,
|
|
27
|
+
navigate: E,
|
|
28
|
+
currentUser: D,
|
|
29
|
+
workspaceId: O,
|
|
30
|
+
tenantId: k,
|
|
31
|
+
organizationId: A,
|
|
32
|
+
apiGatewayUrl: j,
|
|
33
|
+
authToken: M,
|
|
34
|
+
workspaceToken: N,
|
|
35
|
+
eventBus: P,
|
|
36
|
+
getWorkspaceToken: F
|
|
37
|
+
}, L = c(!1), R = c(F);
|
|
38
|
+
R.current = F;
|
|
39
|
+
let z = s(() => {
|
|
40
|
+
let e = new d({
|
|
41
|
+
mutationCache: new u({ onSuccess: () => {
|
|
41
42
|
e.invalidateQueries();
|
|
42
43
|
} }),
|
|
43
44
|
defaultOptions: { queries: {
|
|
@@ -47,23 +48,26 @@ var C = r(null), w = () => {
|
|
|
47
48
|
} }
|
|
48
49
|
});
|
|
49
50
|
return e;
|
|
50
|
-
}, []),
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}),
|
|
51
|
+
}, []), B = s(() => {
|
|
52
|
+
let a = i({
|
|
53
|
+
authToken: M,
|
|
54
|
+
workspaceToken: N,
|
|
55
|
+
workspaceId: O,
|
|
56
|
+
organizationId: A
|
|
57
|
+
}), o = r(j, window.location.origin), s = o.http, c = new m({
|
|
58
|
+
uri: s,
|
|
59
|
+
headers: Object.keys(a).length > 0 ? a : void 0
|
|
60
|
+
}), l = y(({ error: e, operation: t, forward: n }) => {
|
|
57
61
|
if (!e) return;
|
|
58
|
-
let r =
|
|
59
|
-
if (!
|
|
60
|
-
e instanceof Error &&
|
|
62
|
+
let r = w("errors" in e && Array.isArray(e.errors) ? e.errors : []);
|
|
63
|
+
if (!R.current || !r || L.current) {
|
|
64
|
+
e instanceof Error && x(e) && S();
|
|
61
65
|
return;
|
|
62
66
|
}
|
|
63
|
-
return new
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
+
return new g((e) => {
|
|
68
|
+
L.current = !0, R.current().then((r) => {
|
|
69
|
+
if (L.current = !1, !r) {
|
|
70
|
+
S(), e.error(/* @__PURE__ */ Error("Failed to refresh workspace token"));
|
|
67
71
|
return;
|
|
68
72
|
}
|
|
69
73
|
t.setContext(({ headers: e = {} }) => ({ headers: {
|
|
@@ -73,39 +77,40 @@ var C = r(null), w = () => {
|
|
|
73
77
|
let i = n(t).subscribe(e);
|
|
74
78
|
return () => i.unsubscribe();
|
|
75
79
|
}).catch((t) => {
|
|
76
|
-
|
|
80
|
+
L.current = !1, e.error(t);
|
|
77
81
|
});
|
|
78
82
|
});
|
|
79
|
-
});
|
|
80
|
-
return new
|
|
81
|
-
link:
|
|
83
|
+
}), u = o.websocket;
|
|
84
|
+
return new p({
|
|
85
|
+
link: v(({ query: t }) => {
|
|
82
86
|
let n = e(t);
|
|
83
87
|
return n.kind === "OperationDefinition" && n.operation === "subscription";
|
|
84
88
|
}, new t(n({
|
|
85
|
-
url:
|
|
86
|
-
connectionParams: Object.keys(
|
|
87
|
-
})),
|
|
88
|
-
cache: new
|
|
89
|
+
url: u,
|
|
90
|
+
connectionParams: Object.keys(a).length > 0 ? a : void 0
|
|
91
|
+
})), _([l, c])),
|
|
92
|
+
cache: new h(),
|
|
89
93
|
defaultOptions: { watchQuery: { fetchPolicy: "cache-and-network" } }
|
|
90
94
|
});
|
|
91
95
|
}, [
|
|
92
|
-
k,
|
|
93
|
-
A,
|
|
94
96
|
j,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
M,
|
|
98
|
+
N,
|
|
99
|
+
O,
|
|
100
|
+
A
|
|
101
|
+
]), V = /* @__PURE__ */ C(f, {
|
|
102
|
+
client: z,
|
|
103
|
+
children: /* @__PURE__ */ C(T.Provider, {
|
|
104
|
+
value: I,
|
|
105
|
+
children: a
|
|
101
106
|
})
|
|
102
107
|
});
|
|
103
|
-
return
|
|
104
|
-
client:
|
|
105
|
-
children: /* @__PURE__ */
|
|
108
|
+
return o ? V : /* @__PURE__ */ C(b, {
|
|
109
|
+
client: B,
|
|
110
|
+
children: /* @__PURE__ */ C(l, { children: V })
|
|
106
111
|
});
|
|
107
112
|
};
|
|
108
113
|
//#endregion
|
|
109
|
-
export {
|
|
114
|
+
export { D as ExportProvider, E as useExport };
|
|
110
115
|
|
|
111
116
|
//# sourceMappingURL=ExportProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportProvider.js","names":[],"sources":["../../../src/export/providers/ExportProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useRef } from \"react\";\nimport { MemoryRouter } from \"react-router\";\nimport {\n MutationCache,\n QueryClient,\n QueryClientProvider,\n} from \"@tanstack/react-query\";\nimport {\n ApolloClient,\n InMemoryCache,\n HttpLink,\n from,\n split,\n Observable,\n} from \"@apollo/client\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { createClient } from \"graphql-ws\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { ApolloProvider } from \"@apollo/client/react\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\nimport type { ExportRootProps } from \"../types\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n/**\n * Context value for Export microfrontend\n */\ntype ExportContextValue = ExportRootProps;\n\nconst ExportContext = createContext<ExportContextValue | null>(null);\n\n/**\n * Hook to access Export context\n */\nexport const useExport = () => {\n const context = useContext(ExportContext);\n if (!context) {\n throw new Error(\"useExport must be used within ExportProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Export microfrontend\n * Sets up routing, context, Apollo Client, and React Query client\n */\nexport const ExportProvider: FC<PropsWithChildren<ExportRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n}) => {\n const contextValue: ExportContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n };\n\n // Track refreshing state across renders without triggering re-renders\n const isRefreshingRef = useRef(false);\n\n // Stable ref for getWorkspaceToken so it never invalidates the Apollo client memo\n const getWorkspaceTokenRef = useRef(getWorkspaceToken);\n getWorkspaceTokenRef.current = getWorkspaceToken;\n\n // Create QueryClient instance (memoized to avoid recreation on re-renders)\n const queryClient = useMemo(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n });\n return client;\n }, []);\n\n // Create ApolloClient instance (memoized, recreated when tokens change)\n // The workspace gateway requires dual-token auth:\n // Authorization: Bearer <authToken> (platform identity)\n // X-Workspace-Authorization: Bearer <workspaceToken> (workspace access)\n // In dev mode (no tokens), the client adds actor headers directly and uses\n // x-gateway-auth-token so the service trusts them (TRUSTED_GATEWAY_TOKEN=dev-test-secret-2026).\n const apolloClient = useMemo(() => {\n const isDevMode = !authToken && !workspaceToken;\n const headers: Record<string, string> = {};\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n if (workspaceToken) {\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n }\n\n // Dev-mode: inject actor headers directly so the service can authenticate the request\n if (isDevMode) {\n headers[\"x-actor-id\"] = \"00000000-0000-0000-0000-000000000002\";\n headers[\"x-actor-type\"] = \"user\";\n headers[\"x-workspace-id\"] =\n workspaceId ?? \"00000000-0000-0000-0000-000000000001\";\n headers[\"x-gateway-auth-token\"] =\n import.meta.env.VITE_DEV_GATEWAY_TOKEN ?? \"\";\n }\n\n // In dev mode (no tokens) connect directly to the export service to bypass gateway auth\n const serviceUrl = isDevMode\n ? (import.meta.env.VITE_EXPORT_SERVICE_URL ??\n \"http://localhost:3732/graphql\")\n : apiGatewayUrl\n ? `${apiGatewayUrl}/workspaces/graphql`\n : \"/workspaces/graphql\";\n\n const httpLink = new HttpLink({\n uri: serviceUrl,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n\n // Error link: refresh workspace token on TOKEN_EXPIRED and retry the operation\n const errorLink = onError(({ error, operation, forward }) => {\n if (!error) return;\n\n const errors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n const hasAuthError = hasSessionAuthErrors(errors);\n\n if (\n !getWorkspaceTokenRef.current ||\n !hasAuthError ||\n isRefreshingRef.current\n ) {\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n return;\n }\n\n return new Observable((observer) => {\n isRefreshingRef.current = true;\n getWorkspaceTokenRef.current!()\n .then((freshToken) => {\n isRefreshingRef.current = false;\n if (!freshToken) {\n redirectToLogin();\n observer.error(new Error(\"Failed to refresh workspace token\"));\n return;\n }\n // Inject the fresh token into the retry request\n operation.setContext(\n ({\n headers: existingHeaders = {},\n }: {\n headers?: Record<string, string>;\n }) => ({\n headers: {\n ...existingHeaders,\n \"X-Workspace-Authorization\": `Bearer ${freshToken}`,\n },\n }),\n );\n const subscriber = forward(operation).subscribe(observer);\n return () => subscriber.unsubscribe();\n })\n .catch((err: unknown) => {\n isRefreshingRef.current = false;\n observer.error(err);\n });\n });\n });\n\n // WebSocket link for subscriptions (replace http with ws)\n const wsUrl = serviceUrl.replace(/^http/, \"ws\");\n const wsLink = new GraphQLWsLink(\n createClient({\n url: wsUrl,\n connectionParams: Object.keys(headers).length > 0 ? headers : undefined,\n }),\n );\n\n // Split link: use WebSocket for subscriptions, HTTP for queries/mutations\n const splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, httpLink]),\n );\n\n return new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache(),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n },\n });\n }, [\n apiGatewayUrl,\n authToken,\n workspaceToken,\n workspaceId,\n // getWorkspaceToken is intentionally omitted: it's accessed via ref so the\n // Apollo client is not destroyed on every render if the host passes a new\n // function reference each render cycle.\n ]);\n\n // Wrap content with QueryClientProvider and Context.Provider\n const content = (\n <QueryClientProvider client={queryClient}>\n <ExportContext.Provider value={contextValue}>\n {children}\n </ExportContext.Provider>\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // and ApolloProvider so we don't create another one. When standalone (no basePath),\n // use MemoryRouter and ApolloProvider.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter and ApolloProvider, don't create nested ones\n return content;\n }\n\n // Standalone mode: create MemoryRouter and ApolloProvider for independent operation\n return (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportProvider.js","names":[],"sources":["../../../src/export/providers/ExportProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useRef } from \"react\";\nimport { MemoryRouter } from \"react-router\";\nimport {\n MutationCache,\n QueryClient,\n QueryClientProvider,\n} from \"@tanstack/react-query\";\nimport {\n ApolloClient,\n InMemoryCache,\n HttpLink,\n from,\n split,\n Observable,\n} from \"@apollo/client\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { createClient } from \"graphql-ws\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { ApolloProvider } from \"@apollo/client/react\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n exportGatewayHeaders,\n exportGatewayEndpoints,\n} from \"../utils/exportAuthority\";\nimport type { ExportRootProps } from \"../types\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n/**\n * Context value for Export microfrontend\n */\ntype ExportContextValue = ExportRootProps;\n\nconst ExportContext = createContext<ExportContextValue | null>(null);\n\n/**\n * Hook to access Export context\n */\nexport const useExport = () => {\n const context = useContext(ExportContext);\n if (!context) {\n throw new Error(\"useExport must be used within ExportProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Export microfrontend\n * Sets up routing, context, Apollo Client, and React Query client\n */\nexport const ExportProvider: FC<PropsWithChildren<ExportRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n}) => {\n const contextValue: ExportContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n };\n\n // Track refreshing state across renders without triggering re-renders\n const isRefreshingRef = useRef(false);\n\n // Stable ref for getWorkspaceToken so it never invalidates the Apollo client memo\n const getWorkspaceTokenRef = useRef(getWorkspaceToken);\n getWorkspaceTokenRef.current = getWorkspaceToken;\n\n // Create QueryClient instance (memoized to avoid recreation on re-renders)\n const queryClient = useMemo(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n });\n return client;\n }, []);\n\n // Create ApolloClient instance (memoized, recreated when tokens change)\n // The workspace gateway requires dual-token auth:\n // HTTP and subscription traffic use the public gateway with trusted host\n // credentials and explicit workspace/organization context. No subgraph bypass.\n const apolloClient = useMemo(() => {\n const headers = exportGatewayHeaders({\n authToken,\n workspaceToken,\n workspaceId,\n organizationId,\n });\n // Anonymous/missing-token requests still go through the gateway and fail there.\n const endpoints = exportGatewayEndpoints(\n apiGatewayUrl,\n window.location.origin,\n );\n const serviceUrl = endpoints.http;\n\n const httpLink = new HttpLink({\n uri: serviceUrl,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n\n // Error link: refresh workspace token on TOKEN_EXPIRED and retry the operation\n const errorLink = onError(({ error, operation, forward }) => {\n if (!error) return;\n\n const errors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n const hasAuthError = hasSessionAuthErrors(errors);\n\n if (\n !getWorkspaceTokenRef.current ||\n !hasAuthError ||\n isRefreshingRef.current\n ) {\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n return;\n }\n\n return new Observable((observer) => {\n isRefreshingRef.current = true;\n getWorkspaceTokenRef.current!()\n .then((freshToken) => {\n isRefreshingRef.current = false;\n if (!freshToken) {\n redirectToLogin();\n observer.error(new Error(\"Failed to refresh workspace token\"));\n return;\n }\n // Inject the fresh token into the retry request\n operation.setContext(\n ({\n headers: existingHeaders = {},\n }: {\n headers?: Record<string, string>;\n }) => ({\n headers: {\n ...existingHeaders,\n \"X-Workspace-Authorization\": `Bearer ${freshToken}`,\n },\n }),\n );\n const subscriber = forward(operation).subscribe(observer);\n return () => subscriber.unsubscribe();\n })\n .catch((err: unknown) => {\n isRefreshingRef.current = false;\n observer.error(err);\n });\n });\n });\n\n // WebSocket link for subscriptions (replace http with ws)\n const wsUrl = endpoints.websocket;\n const wsLink = new GraphQLWsLink(\n createClient({\n url: wsUrl,\n connectionParams: Object.keys(headers).length > 0 ? headers : undefined,\n }),\n );\n\n // Split link: use WebSocket for subscriptions, HTTP for queries/mutations\n const splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, httpLink]),\n );\n\n return new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache(),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n },\n });\n }, [\n apiGatewayUrl,\n authToken,\n workspaceToken,\n workspaceId,\n organizationId,\n // getWorkspaceToken is intentionally omitted: it's accessed via ref so the\n // Apollo client is not destroyed on every render if the host passes a new\n // function reference each render cycle.\n ]);\n\n // Wrap content with QueryClientProvider and Context.Provider\n const content = (\n <QueryClientProvider client={queryClient}>\n <ExportContext.Provider value={contextValue}>\n {children}\n </ExportContext.Provider>\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // and ApolloProvider so we don't create another one. When standalone (no basePath),\n // use MemoryRouter and ApolloProvider.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter and ApolloProvider, don't create nested ones\n return content;\n }\n\n // Standalone mode: create MemoryRouter and ApolloProvider for independent operation\n return (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -134,6 +134,9 @@ export interface ExportJob {
|
|
|
134
134
|
processedEntities?: number;
|
|
135
135
|
totalSizeBytes?: number;
|
|
136
136
|
parts: ExportPart[];
|
|
137
|
+
archiveFileId?: string;
|
|
138
|
+
downloadUrl?: string;
|
|
139
|
+
downloadExpiresAt?: string;
|
|
137
140
|
templateId?: string;
|
|
138
141
|
filters?: Record<string, unknown>;
|
|
139
142
|
errorMessage?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/export/types/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/export/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DocumentNode } from 'graphql';
|
|
2
|
+
import { ExportJob, ExportScope } from '../types';
|
|
3
|
+
|
|
4
|
+
/** Preserve the typed response alias while selecting the gateway-authorized root. */
|
|
5
|
+
export declare function exportScopeDocument(document: DocumentNode, scope: ExportScope, kind: "create" | "retry"): DocumentNode;
|
|
6
|
+
export declare function exportGatewayHeaders(input: {
|
|
7
|
+
authToken?: string;
|
|
8
|
+
workspaceToken?: string;
|
|
9
|
+
workspaceId?: string;
|
|
10
|
+
organizationId?: string;
|
|
11
|
+
}): Record<string, string>;
|
|
12
|
+
export declare function exportGatewayEndpoints(apiGatewayUrl: string | undefined, pageOrigin: string): {
|
|
13
|
+
http: string;
|
|
14
|
+
websocket: string;
|
|
15
|
+
};
|
|
16
|
+
/** The complete export CTA must never download only the first chunk. */
|
|
17
|
+
export declare function exportArchiveDownload(job: ExportJob | null | undefined, now?: number): string | undefined;
|
|
18
|
+
//# sourceMappingURL=exportAuthority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportAuthority.d.ts","sourceRoot":"","sources":["../../../src/export/utils/exportAuthority.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAavD,qFAAqF;AACrF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,QAAQ,GAAG,OAAO,GACvB,YAAY,CAcd;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQzB;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,UAAU,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAarC;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,EACjC,GAAG,SAAa,GACf,MAAM,GAAG,SAAS,CAyBpB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { visit as e } from "graphql";
|
|
2
|
+
//#region src/export/utils/exportAuthority.ts
|
|
3
|
+
var t = {
|
|
4
|
+
INDIVIDUAL: "createExportJob",
|
|
5
|
+
WORKSPACE_ADMIN: "createWorkspaceExportJob",
|
|
6
|
+
ORG_ADMIN: "createOrganizationExportJob"
|
|
7
|
+
}, n = {
|
|
8
|
+
INDIVIDUAL: "retryExportJob",
|
|
9
|
+
WORKSPACE_ADMIN: "retryWorkspaceExportJob",
|
|
10
|
+
ORG_ADMIN: "retryOrganizationExportJob"
|
|
11
|
+
};
|
|
12
|
+
function r(r, i, a) {
|
|
13
|
+
let o = a === "create" ? "createExportJob" : "retryExportJob", s = (a === "create" ? t : n)[i];
|
|
14
|
+
if (!s) throw Error("Unsupported export scope");
|
|
15
|
+
return e(r, { Field(e) {
|
|
16
|
+
if (e.name.value === o) return {
|
|
17
|
+
...e,
|
|
18
|
+
alias: e.alias ?? e.name,
|
|
19
|
+
name: {
|
|
20
|
+
...e.name,
|
|
21
|
+
value: s
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
} });
|
|
25
|
+
}
|
|
26
|
+
function i(e) {
|
|
27
|
+
let t = {};
|
|
28
|
+
return e.authToken && (t.Authorization = `Bearer ${e.authToken}`), e.workspaceToken && (t["X-Workspace-Authorization"] = `Bearer ${e.workspaceToken}`), e.workspaceId && (t["x-workspace-id"] = e.workspaceId), e.organizationId && (t["x-org-id"] = e.organizationId), t;
|
|
29
|
+
}
|
|
30
|
+
function a(e, t) {
|
|
31
|
+
let n = e?.replace(/\/$/, ""), r = n ? n.endsWith("/workspaces/graphql") ? n : `${n}/workspaces/graphql` : "/workspaces/graphql", i = new URL(r, t);
|
|
32
|
+
if (i.protocol !== "http:" && i.protocol !== "https:") throw Error("Invalid export gateway protocol");
|
|
33
|
+
let a = new URL(i);
|
|
34
|
+
return a.protocol = i.protocol === "https:" ? "wss:" : "ws:", {
|
|
35
|
+
http: i.toString(),
|
|
36
|
+
websocket: a.toString()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function o(e, t = Date.now()) {
|
|
40
|
+
if (e?.status !== "COMPLETED" || e.archiveFormat !== "ZIP" || !e.archiveFileId || !e.downloadUrl || !e.downloadExpiresAt) return;
|
|
41
|
+
let n = Date.parse(e.downloadExpiresAt);
|
|
42
|
+
if (!(!Number.isFinite(n) || n <= t)) try {
|
|
43
|
+
let t = new URL(e.downloadUrl);
|
|
44
|
+
return t.protocol !== "https:" && !(t.protocol === "http:" && ["localhost", "127.0.0.1"].includes(t.hostname)) ? void 0 : e.downloadUrl;
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { o as exportArchiveDownload, a as exportGatewayEndpoints, i as exportGatewayHeaders, r as exportScopeDocument };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=exportAuthority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportAuthority.js","names":[],"sources":["../../../src/export/utils/exportAuthority.ts"],"sourcesContent":["import { visit, type DocumentNode } from \"graphql\";\nimport type { ExportJob, ExportScope } from \"../types\";\n\nconst CREATE = {\n INDIVIDUAL: \"createExportJob\",\n WORKSPACE_ADMIN: \"createWorkspaceExportJob\",\n ORG_ADMIN: \"createOrganizationExportJob\",\n} satisfies Record<ExportScope, string>;\nconst RETRY = {\n INDIVIDUAL: \"retryExportJob\",\n WORKSPACE_ADMIN: \"retryWorkspaceExportJob\",\n ORG_ADMIN: \"retryOrganizationExportJob\",\n} satisfies Record<ExportScope, string>;\n\n/** Preserve the typed response alias while selecting the gateway-authorized root. */\nexport function exportScopeDocument(\n document: DocumentNode,\n scope: ExportScope,\n kind: \"create\" | \"retry\",\n): DocumentNode {\n const original = kind === \"create\" ? \"createExportJob\" : \"retryExportJob\";\n const target = (kind === \"create\" ? CREATE : RETRY)[scope];\n if (!target) throw new Error(\"Unsupported export scope\");\n return visit(document, {\n Field(node) {\n if (node.name.value !== original) return;\n return {\n ...node,\n alias: node.alias ?? node.name,\n name: { ...node.name, value: target },\n };\n },\n });\n}\n\nexport function exportGatewayHeaders(input: {\n authToken?: string;\n workspaceToken?: string;\n workspaceId?: string;\n organizationId?: string;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (input.authToken) headers.Authorization = `Bearer ${input.authToken}`;\n if (input.workspaceToken)\n headers[\"X-Workspace-Authorization\"] = `Bearer ${input.workspaceToken}`;\n if (input.workspaceId) headers[\"x-workspace-id\"] = input.workspaceId;\n if (input.organizationId) headers[\"x-org-id\"] = input.organizationId;\n return headers;\n}\n\nexport function exportGatewayEndpoints(\n apiGatewayUrl: string | undefined,\n pageOrigin: string,\n): { http: string; websocket: string } {\n const base = apiGatewayUrl?.replace(/\\/$/, \"\");\n const endpoint = base\n ? base.endsWith(\"/workspaces/graphql\")\n ? base\n : `${base}/workspaces/graphql`\n : \"/workspaces/graphql\";\n const http = new URL(endpoint, pageOrigin);\n if (http.protocol !== \"http:\" && http.protocol !== \"https:\")\n throw new Error(\"Invalid export gateway protocol\");\n const websocket = new URL(http);\n websocket.protocol = http.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return { http: http.toString(), websocket: websocket.toString() };\n}\n\n/** The complete export CTA must never download only the first chunk. */\nexport function exportArchiveDownload(\n job: ExportJob | null | undefined,\n now = Date.now(),\n): string | undefined {\n if (\n job?.status !== \"COMPLETED\" ||\n job.archiveFormat !== \"ZIP\" ||\n !job.archiveFileId ||\n !job.downloadUrl ||\n !job.downloadExpiresAt\n )\n return;\n const expiry = Date.parse(job.downloadExpiresAt);\n if (!Number.isFinite(expiry) || expiry <= now) return;\n try {\n const url = new URL(job.downloadUrl);\n if (\n url.protocol !== \"https:\" &&\n !(\n url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\"].includes(url.hostname)\n )\n )\n return;\n return job.downloadUrl;\n } catch {\n return;\n }\n}\n"],"mappings":";;AAGA,IAAM,IAAS;CACb,YAAY;CACZ,iBAAiB;CACjB,WAAW;CACZ,EACK,IAAQ;CACZ,YAAY;CACZ,iBAAiB;CACjB,WAAW;CACZ;AAGD,SAAgB,EACd,GACA,GACA,GACc;CACd,IAAM,IAAW,MAAS,WAAW,oBAAoB,kBACnD,KAAU,MAAS,WAAW,IAAS,GAAO;AACpD,KAAI,CAAC,EAAQ,OAAU,MAAM,2BAA2B;AACxD,QAAO,EAAM,GAAU,EACrB,MAAM,GAAM;AACN,QAAK,KAAK,UAAU,EACxB,QAAO;GACL,GAAG;GACH,OAAO,EAAK,SAAS,EAAK;GAC1B,MAAM;IAAE,GAAG,EAAK;IAAM,OAAO;IAAQ;GACtC;IAEJ,CAAC;;AAGJ,SAAgB,EAAqB,GAKV;CACzB,IAAM,IAAkC,EAAE;AAM1C,QALI,EAAM,cAAW,EAAQ,gBAAgB,UAAU,EAAM,cACzD,EAAM,mBACR,EAAQ,+BAA+B,UAAU,EAAM,mBACrD,EAAM,gBAAa,EAAQ,oBAAoB,EAAM,cACrD,EAAM,mBAAgB,EAAQ,cAAc,EAAM,iBAC/C;;AAGT,SAAgB,EACd,GACA,GACqC;CACrC,IAAM,IAAO,GAAe,QAAQ,OAAO,GAAG,EACxC,IAAW,IACb,EAAK,SAAS,sBAAsB,GAClC,IACA,GAAG,EAAK,uBACV,uBACE,IAAO,IAAI,IAAI,GAAU,EAAW;AAC1C,KAAI,EAAK,aAAa,WAAW,EAAK,aAAa,SACjD,OAAU,MAAM,kCAAkC;CACpD,IAAM,IAAY,IAAI,IAAI,EAAK;AAE/B,QADA,EAAU,WAAW,EAAK,aAAa,WAAW,SAAS,OACpD;EAAE,MAAM,EAAK,UAAU;EAAE,WAAW,EAAU,UAAU;EAAE;;AAInE,SAAgB,EACd,GACA,IAAM,KAAK,KAAK,EACI;AACpB,KACE,GAAK,WAAW,eAChB,EAAI,kBAAkB,SACtB,CAAC,EAAI,iBACL,CAAC,EAAI,eACL,CAAC,EAAI,kBAEL;CACF,IAAM,IAAS,KAAK,MAAM,EAAI,kBAAkB;AAC5C,QAAC,OAAO,SAAS,EAAO,IAAI,KAAU,GAC1C,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,EAAI,YAAY;AASpC,SAPE,EAAI,aAAa,YACjB,EACE,EAAI,aAAa,WACjB,CAAC,aAAa,YAAY,CAAC,SAAS,EAAI,SAAS,IAGnD,SACK,EAAI;SACL;AACN"}
|