From ad3709c56608228eaf09088d8daf158b1a16f845 Mon Sep 17 00:00:00 2001 From: none23 Date: Sun, 9 Aug 2026 16:58:42 +0400 Subject: [PATCH 1/2] Show expired artifact state --- client/index.tsx | 36 ++++++++++++++++++++++++--- server/index.ts | 64 ++++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 29 deletions(-) diff --git a/client/index.tsx b/client/index.tsx index b04cad6..0ec69b8 100644 --- a/client/index.tsx +++ b/client/index.tsx @@ -597,7 +597,8 @@ function ArtifactFrame({ requestedSlug }: { requestedSlug?: string }) { const artifact = client.useQuery("artifactBySlug", slug); const acceptArtifactAccess = client.useMutation("acceptArtifactAccess"); const accessBootstrap = useAccessBootstrap(); - const [accessState, setAccessState] = useState<"idle" | "accepting" | "accepted" | "denied">("idle"); + const [accessState, setAccessState] = useState<"idle" | "accepting" | "accepted" | "expired" | "denied">("idle"); + const [expiredAt, setExpiredAt] = useState(""); const [copied, setCopied] = useState(false); useEffect(() => { @@ -628,7 +629,14 @@ function ArtifactFrame({ requestedSlug }: { requestedSlug?: string }) { setAccessState("accepting"); void acceptArtifactAccess(slug) - .then((result) => setAccessState(result.accepted ? "accepted" : "denied")) + .then((result) => { + if (result.status === "expired") { + setExpiredAt(result.expiredAt); + setAccessState("expired"); + return; + } + setAccessState(result.status === "accepted" ? "accepted" : "denied"); + }) .catch(() => setAccessState("denied")); }, [ artifact, @@ -639,6 +647,14 @@ function ArtifactFrame({ requestedSlug }: { requestedSlug?: string }) { accessBootstrap.state ]); + useEffect(() => { + if (artifact !== null || accessState !== "accepted") { + return; + } + const timeout = window.setTimeout(() => setAccessState("denied"), 5000); + return () => window.clearTimeout(timeout); + }, [artifact, accessState]); + if (artifact === undefined) { return
Opening artifact…
; } @@ -646,11 +662,25 @@ function ArtifactFrame({ requestedSlug }: { requestedSlug?: string }) { if (auth.isGuest) { return ; } + if (accessState === "expired") { + return ( +
+

Expired

+

This artifact has expired.

+

+ It expired {formatDate(expiredAt)}. Ask the owner to republish it with a longer lifetime. +

+
+ Back +
+
+ ); + } if ( accessState === "accepting" || accessState === "accepted" || accessBootstrap.state === "claiming" || - accessBootstrap.state === "claimed" + (accessBootstrap.state === "claimed" && accessState === "idle") ) { return
Verifying shared access…
; } diff --git a/server/index.ts b/server/index.ts index 337e480..ea4d6e6 100644 --- a/server/index.ts +++ b/server/index.ts @@ -108,6 +108,11 @@ type PublishInput = { expiresInSeconds?: number | null; }; +type ArtifactAccessResult = + | { status: "accepted" } + | { status: "expired"; expiredAt: string } + | { status: "unavailable" }; + function ownerEmails(ctx: EnvironmentContext): string[] { const configured = (ctx.env.OWNER_EMAILS ?? "") .split(",") @@ -617,27 +622,21 @@ export default capsule({ claimed: await claimConfiguredWorkspaceViewer(ctx) })), - acceptArtifactAccess: mutation(async (ctx, slugInput: string) => { + acceptArtifactAccess: mutation(async ( + ctx, + slugInput: string + ): Promise => { const identity = authenticatedIdentity(ctx); - if (!identity) { - return { accepted: false }; - } - if (await claimConfiguredOwner(ctx)) { - return { accepted: true }; - } - if (await claimConfiguredWorkspaceViewer(ctx)) { - return { accepted: true }; - } - const slug = cleanSlug(slugInput); const artifact = await ctx.db.artifacts .withIndex("by_slug", (q) => q.eq("slug", slug)) .first(); - if (!artifact || isArtifactExpired(artifact.expiresAt)) { - return { accepted: false }; + if (!artifact) { + return { status: "unavailable" }; } - if (artifact.isPublic === true) { - return { accepted: true }; + + if (!identity) { + return { status: "unavailable" }; } const sharedWith = parseSharedEmails(artifact.sharedWith); @@ -649,18 +648,31 @@ export default capsule({ ? "domain" : null; const ruleValue = ruleType === "email" ? identity.email : domain; - if (!ruleType) { - return { accepted: false }; + const isOwner = await claimConfiguredOwner(ctx); + const isWorkspaceViewer = isOwner + ? false + : await claimConfiguredWorkspaceViewer(ctx); + const hasGrant = await validArtifactGrant( + ctx, + artifact.id, + identity.userId, + sharedWith, + sharedDomains + ); + const canView = + artifact.isPublic === true || + isOwner || + isWorkspaceViewer || + hasGrant || + ruleType !== null; + if (!canView) { + return { status: "unavailable" }; + } + if (isArtifactExpired(artifact.expiresAt)) { + return { status: "expired", expiredAt: artifact.expiresAt }; } - const grants = await ctx.db.artifactGrants - .withIndex("by_artifact_user", (q) => - q.eq("artifactId", artifact.id).eq("userId", identity.userId) - ) - .collect(); - if (!grants.some((grant) => - grant.ruleType === ruleType && grant.ruleValue === ruleValue - )) { + if (!isOwner && !isWorkspaceViewer && !hasGrant && ruleType) { await ctx.db.artifactGrants.insert({ artifactId: artifact.id, userId: identity.userId, @@ -668,7 +680,7 @@ export default capsule({ ruleValue }); } - return { accepted: true }; + return { status: "accepted" }; }), publishArtifact: mutation(async (ctx, input: PublishInput) => { From 73a8f8b58b329bbcee396b706855c47c57b32847 Mon Sep 17 00:00:00 2001 From: none23 Date: Sun, 9 Aug 2026 19:05:48 +0400 Subject: [PATCH 2/2] Reset artifact state between links --- client/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/index.tsx b/client/index.tsx index 0ec69b8..4af503d 100644 --- a/client/index.tsx +++ b/client/index.tsx @@ -590,10 +590,8 @@ function AccessControl({ artifact }: { artifact: ViewedArtifact }) { ); } -function ArtifactFrame({ requestedSlug }: { requestedSlug?: string }) { +function ArtifactFrame({ slug }: { slug: string }) { const auth = useAuth(); - const params = useParams<{ slug: string }>(); - const slug = cleanSlug(requestedSlug ?? params.slug ?? ""); const artifact = client.useQuery("artifactBySlug", slug); const acceptArtifactAccess = client.useMutation("acceptArtifactAccess"); const accessBootstrap = useAccessBootstrap(); @@ -764,7 +762,9 @@ function SignedInRoot() { } function ArtifactPage({ requestedSlug }: { requestedSlug?: string }) { - return ; + const params = useParams<{ slug: string }>(); + const slug = cleanSlug(requestedSlug ?? params.slug ?? ""); + return ; } function AppContent() {