Skip to content

Commit 09295c5

Browse files
yoen-veltclaude
andcommitted
fix: use useCurrentUser instead of useVeltInitState to prevent deadlock
Replace useVeltInitState with useCurrentUser in all VeltInitializeDocument.tsx files. useVeltInitState requires both user AND document to be initialized, which creates a deadlock when gating setDocuments() on it. useCurrentUser only waits for Velt auth to complete, allowing setDocuments() to be called after authentication without circular dependency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent b4fd6be commit 09295c5

File tree

18 files changed

+103
-158
lines changed

18 files changed

+103
-158
lines changed

apps/master-sample-app/components/viewer/iframe-pair.tsx

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,6 @@
11
"use client"
22

3-
import { useState, useRef, useEffect } from "react"
4-
5-
// Time to wait for Velt SDK auth to complete before refreshing
6-
// validateclient takes ~7-8 seconds, add buffer
7-
const VELT_AUTH_WAIT_MS = 8000
8-
9-
// Check if a demo origin has been initialized (Velt auth cached)
10-
function isDemoInitialized(url: string): boolean {
11-
try {
12-
const origin = new URL(url).origin
13-
const key = `velt-initialized-${origin}`
14-
return localStorage.getItem(key) === 'true'
15-
} catch {
16-
return false
17-
}
18-
}
19-
20-
// Mark a demo origin as initialized
21-
function markDemoInitialized(url: string): void {
22-
try {
23-
const origin = new URL(url).origin
24-
const key = `velt-initialized-${origin}`
25-
localStorage.setItem(key, 'true')
26-
} catch {
27-
// ignore
28-
}
29-
}
3+
import { useState, useRef } from "react"
304

315
interface IframePairProps {
326
url?: string
@@ -41,29 +15,11 @@ export function IframePair({
4115
height = "982px",
4216
displayMode = 'dual'
4317
}: IframePairProps) {
44-
const [isLoading1, setIsLoading1] = useState(true)
45-
const [isLoading2, setIsLoading2] = useState(true)
4618
const [error1, setError1] = useState(false)
4719
const [error2, setError2] = useState(false)
48-
// If demo was already initialized, skip the wait/refresh cycle
49-
const [hasRefreshed1, setHasRefreshed1] = useState(() => url ? isDemoInitialized(url) : false)
50-
const [hasRefreshed2, setHasRefreshed2] = useState(() => {
51-
const targetUrl = secondUrl || url
52-
return targetUrl ? isDemoInitialized(targetUrl) : false
53-
})
5420

5521
const iframe1Ref = useRef<HTMLIFrameElement>(null)
5622
const iframe2Ref = useRef<HTMLIFrameElement>(null)
57-
const refreshTimer1 = useRef<NodeJS.Timeout | null>(null)
58-
const refreshTimer2 = useRef<NodeJS.Timeout | null>(null)
59-
60-
// Cleanup timers on unmount
61-
useEffect(() => {
62-
return () => {
63-
if (refreshTimer1.current) clearTimeout(refreshTimer1.current)
64-
if (refreshTimer2.current) clearTimeout(refreshTimer2.current)
65-
}
66-
}, [])
6723

6824
// Safety check: don't render if URL is not provided
6925
if (!url) {
@@ -76,56 +32,10 @@ export function IframePair({
7632

7733
const finalSecondUrl = secondUrl || url
7834

79-
const handleIframeLoad = (iframeNum: number) => {
80-
if (iframeNum === 1) {
81-
setError1(false)
82-
83-
if (!hasRefreshed1) {
84-
// First load: wait for Velt auth to complete, then refresh
85-
refreshTimer1.current = setTimeout(() => {
86-
if (iframe1Ref.current && url) {
87-
setHasRefreshed1(true)
88-
// Force refresh by resetting src
89-
iframe1Ref.current.src = url
90-
}
91-
}, VELT_AUTH_WAIT_MS)
92-
} else {
93-
// Second load (after refresh): Velt auth should be cached, ready to show
94-
setIsLoading1(false)
95-
// Mark this demo origin as initialized for future visits
96-
if (url) markDemoInitialized(url)
97-
}
98-
} else {
99-
setError2(false)
100-
101-
if (!hasRefreshed2) {
102-
// First load: wait for Velt auth to complete, then refresh
103-
refreshTimer2.current = setTimeout(() => {
104-
if (iframe2Ref.current) {
105-
const refreshUrl = secondUrl || url
106-
if (refreshUrl) {
107-
setHasRefreshed2(true)
108-
// Force refresh by resetting src
109-
iframe2Ref.current.src = refreshUrl
110-
}
111-
}
112-
}, VELT_AUTH_WAIT_MS)
113-
} else {
114-
// Second load (after refresh): Velt auth should be cached, ready to show
115-
setIsLoading2(false)
116-
// Mark this demo origin as initialized for future visits
117-
const targetUrl = secondUrl || url
118-
if (targetUrl) markDemoInitialized(targetUrl)
119-
}
120-
}
121-
}
122-
12335
const handleIframeError = (iframeNum: number) => {
12436
if (iframeNum === 1) {
125-
setIsLoading1(false)
12637
setError1(true)
12738
} else {
128-
setIsLoading2(false)
12939
setError2(true)
13040
}
13141
}
@@ -135,11 +45,6 @@ export function IframePair({
13545
return (
13646
<div className="h-full relative">
13747
<div className="rounded-lg border border-border bg-card overflow-hidden h-full">
138-
{!hasRefreshed1 && (
139-
<div className="absolute inset-0 flex items-center justify-center z-10 bg-background">
140-
<div className="text-muted-foreground">Initializing...</div>
141-
</div>
142-
)}
14348
{error1 && (
14449
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
14550
<div className="text-destructive">Failed to load demo</div>
@@ -152,7 +57,6 @@ export function IframePair({
15257
style={{ maxHeight: height }}
15358
title="Demo"
15459
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
155-
onLoad={() => handleIframeLoad(1)}
15660
onError={() => handleIframeError(1)}
15761
/>
15862
</div>
@@ -164,11 +68,6 @@ export function IframePair({
16468
return (
16569
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 h-full">
16670
<div className="rounded-lg border border-border bg-card overflow-hidden relative">
167-
{!hasRefreshed1 && (
168-
<div className="absolute inset-0 flex items-center justify-center z-10 bg-background">
169-
<div className="text-muted-foreground text-sm">Initializing demo...</div>
170-
</div>
171-
)}
17271
{error1 && (
17372
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
17473
<div className="text-destructive text-sm">Failed to load demo 1</div>
@@ -181,16 +80,10 @@ export function IframePair({
18180
style={{ maxHeight: height }}
18281
title="Demo 1"
18382
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
184-
onLoad={() => handleIframeLoad(1)}
18583
onError={() => handleIframeError(1)}
18684
/>
18785
</div>
18886
<div className="rounded-lg border border-border bg-card overflow-hidden relative">
189-
{!hasRefreshed2 && (
190-
<div className="absolute inset-0 flex items-center justify-center z-10 bg-background">
191-
<div className="text-muted-foreground text-sm">Initializing demo...</div>
192-
</div>
193-
)}
19487
{error2 && (
19588
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
19689
<div className="text-destructive text-sm">Failed to load demo 2</div>
@@ -203,7 +96,6 @@ export function IframePair({
20396
style={{ maxHeight: height }}
20497
title="Demo 2"
20598
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
206-
onLoad={() => handleIframeLoad(2)}
20799
onError={() => handleIframeError(2)}
208100
/>
209101
</div>
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useCurrentDocument } from '@/app/document/useCurrentDocument';
55
import { useAppUser } from '@/app/userAuth/useAppUser';
66

@@ -11,13 +11,16 @@ export default function VeltInitializeDocument() {
1111
// [Velt] Get document setter hook
1212
const { setDocuments } = useSetDocuments();
1313

14+
// [Velt] Wait for Velt user to be authenticated before setting document
15+
const veltUser = useCurrentUser();
16+
1417
// [Velt] Set document in Velt. This is the resource where all Velt collaboration data will be scoped.
1518
useEffect(() => {
16-
if (!user || !documentId || !documentName) return;
19+
if (!veltUser || !user || !documentId || !documentName) return;
1720
setDocuments([
1821
{ id: documentId, metadata: { documentName: documentName || 'Untitled' } },
1922
]);
20-
}, [user, setDocuments, documentId, documentName]);
23+
}, [veltUser, user, setDocuments, documentId, documentName]);
2124

2225
return null;
2326
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useCurrentDocument } from '@/app/document/useCurrentDocument';
55

66
export default function VeltInitializeDocument() {
@@ -9,14 +9,17 @@ export default function VeltInitializeDocument() {
99
// [Velt] Get document setter hook
1010
const { setDocuments } = useSetDocuments();
1111

12+
// [Velt] Wait for Velt user to be authenticated before setting document
13+
const veltUser = useCurrentUser();
14+
1215
// [Velt] Set document in Velt. This is the resource where all Velt collaboration data will be scoped.
1316
useEffect(() => {
14-
if (!documentId || !documentName) return;
17+
if (!veltUser || !documentId || !documentName) return;
1518

1619
setDocuments([
1720
{ id: documentId, metadata: { documentName: documentName } },
1821
]);
19-
}, [setDocuments, documentId, documentName]);
22+
}, [veltUser, setDocuments, documentId, documentName]);
2023

2124
return null;
2225
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
"use client";
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useJobs, JOBS_LIST_DOCUMENT_ID, JOBS_LIST_DOCUMENT_NAME } from '@/app/document/JobsContext';
55

66
export default function VeltInitializeDocument() {
77
// [Velt] Get document setter hook
88
const { setDocuments } = useSetDocuments();
99

10+
// [Velt] Wait for Velt user to be authenticated before setting document
11+
const veltUser = useCurrentUser();
12+
1013
// [Velt] Get the selected job and page state
1114
const { selectedJob, isOnJobsListPage } = useJobs();
1215

1316
// [Velt] Set document in Velt based on current page
1417
useEffect(() => {
18+
if (!veltUser) return;
1519
if (isOnJobsListPage) {
1620
setDocuments([
1721
{ id: JOBS_LIST_DOCUMENT_ID, metadata: { documentName: JOBS_LIST_DOCUMENT_NAME } },
@@ -21,7 +25,7 @@ export default function VeltInitializeDocument() {
2125
{ id: selectedJob.id, metadata: { documentName: selectedJob.jobName } },
2226
]);
2327
}
24-
}, [setDocuments, isOnJobsListPage, selectedJob]);
28+
}, [veltUser, setDocuments, isOnJobsListPage, selectedJob]);
2529

2630
return null;
2731
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
"use client";
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useJobs, JOBS_LIST_DOCUMENT_ID, JOBS_LIST_DOCUMENT_NAME } from '@/app/document/JobsContext';
55

66
export default function VeltInitializeDocument() {
77
// [Velt] Get document setter hook
88
const { setDocuments } = useSetDocuments();
99

10+
// [Velt] Wait for Velt user to be authenticated before setting document
11+
const veltUser = useCurrentUser();
12+
1013
// [Velt] Get the selected job and page state
1114
const { selectedJob, isOnJobsListPage } = useJobs();
1215

1316
// [Velt] Set document in Velt based on current page
1417
useEffect(() => {
18+
if (!veltUser) return;
1519
if (isOnJobsListPage) {
1620
setDocuments([
1721
{ id: JOBS_LIST_DOCUMENT_ID, metadata: { documentName: JOBS_LIST_DOCUMENT_NAME } },
@@ -21,7 +25,7 @@ export default function VeltInitializeDocument() {
2125
{ id: selectedJob.id, metadata: { documentName: selectedJob.jobName } },
2226
]);
2327
}
24-
}, [setDocuments, isOnJobsListPage, selectedJob]);
28+
}, [veltUser, setDocuments, isOnJobsListPage, selectedJob]);
2529

2630
return null;
2731
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useCurrentDocument } from '@/app/document/useCurrentDocument';
55
import { useAppUser } from '@/app/userAuth/useAppUser';
66

@@ -11,13 +11,16 @@ export default function VeltInitializeDocument() {
1111
// [Velt] Get document setter hook
1212
const { setDocuments } = useSetDocuments();
1313

14+
// [Velt] Wait for Velt user to be authenticated before setting document
15+
const veltUser = useCurrentUser();
16+
1417
// [Velt] Set document in Velt. This is the resource where all Velt collaboration data will be scoped.
1518
useEffect(() => {
16-
if (!user || !documentId || !documentName) return;
19+
if (!veltUser || !user || !documentId || !documentName) return;
1720
setDocuments([
1821
{ id: documentId, metadata: { documentName: documentName || 'Untitled' } },
1922
]);
20-
}, [user, setDocuments, documentId, documentName]);
23+
}, [veltUser, user, setDocuments, documentId, documentName]);
2124

2225
return null;
2326
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useCurrentDocument } from '@/app/document/useCurrentDocument';
55
import { useAppUser } from '@/app/userAuth/useAppUser';
66

@@ -11,13 +11,16 @@ export default function VeltInitializeDocument() {
1111
// [Velt] Get document setter hook
1212
const { setDocuments } = useSetDocuments();
1313

14+
// [Velt] Wait for Velt user to be authenticated before setting document
15+
const veltUser = useCurrentUser();
16+
1417
// [Velt] Set document in Velt. This is the resource where all Velt collaboration data will be scoped.
1518
useEffect(() => {
16-
if (!user || !documentId || !documentName) return;
19+
if (!veltUser || !user || !documentId || !documentName) return;
1720
setDocuments([
1821
{ id: documentId, metadata: { documentName: documentName || 'Untitled' } },
1922
]);
20-
}, [user, setDocuments, documentId, documentName]);
23+
}, [veltUser, user, setDocuments, documentId, documentName]);
2124

2225
return null;
2326
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import { useEffect } from 'react';
3-
import { useSetDocuments } from '@veltdev/react';
3+
import { useSetDocuments, useCurrentUser } from '@veltdev/react';
44
import { useCurrentDocument } from '@/app/document/useCurrentDocument';
55
import { useAppUser } from '@/app/userAuth/useAppUser';
66

@@ -11,13 +11,16 @@ export default function VeltInitializeDocument() {
1111
// [Velt] Get document setter hook
1212
const { setDocuments } = useSetDocuments();
1313

14+
// [Velt] Wait for Velt user to be authenticated before setting document
15+
const veltUser = useCurrentUser();
16+
1417
// [Velt] Set document in Velt. This is the resource where all Velt collaboration data will be scoped.
1518
useEffect(() => {
16-
if (!user || !documentId || !documentName) return;
19+
if (!veltUser || !user || !documentId || !documentName) return;
1720
setDocuments([
1821
{ id: documentId, metadata: { documentName: documentName || 'Untitled' } },
1922
]);
20-
}, [user, setDocuments, documentId, documentName]);
23+
}, [veltUser, user, setDocuments, documentId, documentName]);
2124

2225
return null;
2326
}

0 commit comments

Comments
 (0)