Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 129 additions & 6 deletions apps/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {createSubscriptionRequest, registerSubscription, unregisterSubscription} from '@sanity/sdk'
import {SanityApp, SanityConfig} from '@sanity/sdk-react'
import {SanityApp, SanityConfig, useFrameConnection} from '@sanity/sdk-react'
import {Spinner, ThemeProvider} from '@sanity/ui'
import {buildTheme} from '@sanity/ui/theme'
import {type JSX, Suspense, useState} from 'react'
import {type JSX, Suspense, useState, useEffect, useRef, useCallback} from 'react'
import {registerSubscription, unregisterSubscription, createSubscriptionRequest} from '@sanity/sdk'

const theme = buildTheme({})

Expand All @@ -21,10 +21,132 @@ const devConfigs: SanityConfig[] = [
},
]

// Message types for iframe communication
type QueryRequestMessage = {
type: 'dashboard/v1/query/request'
data: {
queryId: string
queryOptions: any
requestId: string
}
}

type QueryResponseMessage = {
type: 'dashboard/v1/query/response'
data: {
requestId: string
data: unknown
error?: string
subscriptionId: string
}
}

// SharedWorker test component
function SharedWorkerTest() {
function SharedWorkerTest({iframeRef}: {iframeRef: React.RefObject<HTMLIFrameElement | null>}) {
const [subscriptionId, setSubscriptionId] = useState<string | null>(null)
const [status, setStatus] = useState<string>('Ready to test')
const [connectionStatus, setConnectionStatus] = useState<string>('Not connected')
const connectionRef = useRef<(() => void) | null>(null)

// Stable status handler
const handleStatus = useCallback((status: string) => {
setConnectionStatus(status)
console.log('[Dashboard] Connection status:', status)
}, [])

// Stable message handler
const handleQueryRequest = useCallback(async (data: any) => {
console.log('[Dashboard] Received query request:', data)

try {
// Create a subscription request from the incoming query data
const subscription = createSubscriptionRequest({
storeName: 'query',
projectId: data.queryOptions.projectId,
dataset: data.queryOptions.dataset,
params: {
query: data.queryOptions.query,
options: data.queryOptions.params || {},
},
appId: 'dashboard-app',
})

console.log('[Dashboard] Creating subscription for query:', subscription)

// Register the subscription with the SharedWorker (it will handle deduplication)
const subscriptionId = await registerSubscription(subscription)
console.log('[Dashboard] Subscription registered with ID:', subscriptionId)

// Return the subscription ID and any initial data
return {
requestId: data.requestId,
subscriptionId,
data: {message: 'Query subscription created successfully'},
}
} catch (error) {
console.error('[Dashboard] Error handling query request:', error)
return {
requestId: data.requestId,
error: error instanceof Error ? error.message : String(error),
subscriptionId: null,
}
}
}, [])

const {connect} = useFrameConnection<
QueryResponseMessage,
QueryRequestMessage
>({
name: 'dashboard',
connectTo: 'sdk-app',
targetOrigin: '*',
onStatus: handleStatus,
heartbeat: false, // Disable heartbeat to reduce cycling
onMessage: {
'dashboard/v1/query/request': handleQueryRequest,
},
})

useEffect(() => {
const handleIframeLoad = () => {
// Clean up any existing connection
if (connectionRef.current) {
connectionRef.current()
connectionRef.current = null
}

// Wait for iframe to be fully loaded
setTimeout(() => {
if (iframeRef.current?.contentWindow) {
try {
const cleanup = connect(iframeRef.current.contentWindow)
connectionRef.current = cleanup
console.log('[Dashboard] Connected to SDK app iframe')
} catch (error) {
console.error('[Dashboard] Failed to connect to iframe:', error)
}
}
}, 100)
}

const iframe = iframeRef.current
if (iframe) {
iframe.addEventListener('load', handleIframeLoad)

// If iframe is already loaded, connect immediately
if (iframe.contentDocument?.readyState === 'complete') {
handleIframeLoad()
}

return () => {
if (connectionRef.current) {
connectionRef.current()
connectionRef.current = null
}
iframe.removeEventListener('load', handleIframeLoad)
}
}
}, [connect])

const testSubscription = async () => {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -69,7 +191,8 @@ function SharedWorkerTest() {
<div style={{padding: 12, borderBottom: '1px solid #eee'}}>
<div>Dashboard (iframes sdk-app below)</div>
<div style={{marginTop: 8, fontSize: '14px'}}>
<div>SharedWorker Test:</div>
<div>Comlink Connection Status: {connectionStatus}</div>
<div style={{marginTop: 8}}>SharedWorker Test:</div>
<div style={{marginTop: 4}}>
<button onClick={testSubscription} disabled={!!subscriptionId}>
Test Subscription
Expand Down Expand Up @@ -106,7 +229,7 @@ export default function App(): JSX.Element {
flexDirection: 'column',
}}
>
<SharedWorkerTest />
<SharedWorkerTest iframeRef={iframeRef} />
<iframe
title="sdk-app"
src="http://localhost:3341/"
Expand Down
67 changes: 62 additions & 5 deletions apps/sdk-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {SanityApp, SanityConfig} from '@sanity/sdk-react'
import {SanityApp, SanityConfig, useQuery, useWindowConnection} from '@sanity/sdk-react'
import {Spinner, ThemeProvider} from '@sanity/ui'
import {buildTheme} from '@sanity/ui/theme'
import {type JSX, Suspense} from 'react'
import {type JSX, useEffect, useRef, useState} from 'react'

const theme = buildTheme({})

Expand All @@ -20,13 +20,70 @@
},
]

// Message types for iframe communication
type QueryRequestMessage = {
type: 'dashboard/v1/query/request'
data: {
queryId: string
queryOptions: any

Check failure on line 28 in apps/sdk-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
requestId: string
}
}

type QueryResponseMessage = {
type: 'dashboard/v1/query/response'
data: {
requestId: string
data: unknown
error?: string
subscriptionId: string
}
}

// Test component to demonstrate query forwarding
function QueryTest() {

// hack -- something in the node setup in the query store has a race condition
useWindowConnection<QueryResponseMessage, QueryRequestMessage>({
name: 'sdk-app',
connectTo: 'dashboard',
})

// This query should be forwarded to Dashboard when in iframe context

Check failure on line 52 in apps/sdk-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

'isPending' is assigned a value but never used. Allowed unused vars must match /^_/u
const {data, isPending} = useQuery({
query: '*[_type == "movie"][0...5]{_id, title, releaseYear}',
projectId: 'ppsg7ml5',
dataset: 'test',
})

Check failure on line 58 in apps/sdk-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
console.log('data', data)

return (
<div style={{padding: 16}}>
<h2>SDK App - Query Test</h2>
<div style={{marginTop: 8}}>
{/* <strong>Query Status:</strong> {isPending ? 'Loading...' : 'Loaded'} */}
</div>
<div style={{marginTop: 8}}>
<strong>Data:</strong>
<pre style={{marginTop: 4, padding: 8, backgroundColor: '#f5f5f5', borderRadius: 4, fontSize: '12px'}}>
{/* {JSON.stringify(data, null, 2)} */}
</pre>
</div>
<div style={{marginTop: 8, fontSize: '12px', color: '#666'}}>
Check the browser console to see the query forwarding logs!
</div>
</div>
)
}

export default function App(): JSX.Element {
return (
<ThemeProvider theme={theme}>
<SanityApp fallback={<Spinner />} config={devConfigs}>
<Suspense>
<div style={{padding: 16}}>SDK app scaffold</div>
</Suspense>
<div style={{height: '100vh', width: '100vw', overflow: 'auto'}}>
<QueryTest />
</div>
</SanityApp>
</ThemeProvider>
)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/comlink/node/comlinkNodeStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('nodeStore', () => {
initialState.nodes.set('test-node', {
options: {name: 'test-node', connectTo: 'parent'},
node: mockNode,
refCount: 1,
status: 'idle',
})

const cleanup = comlinkNodeStore.initialize?.({
Expand Down
Loading
Loading