Skip to content

Commit ce11a0e

Browse files
committed
feat: add service worker store and script
1 parent f80d89e commit ce11a0e

File tree

12 files changed

+886
-5
lines changed

12 files changed

+886
-5
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"prettier": "@sanity/prettier-config",
1515
"dependencies": {
16+
"@sanity/sdk": "workspace:*",
1617
"@sanity/sdk-react": "workspace:*",
1718
"@sanity/ui": "^2.15.13",
1819
"react": "^18.3.1",

apps/dashboard/src/App.tsx

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {SanityApp, SanityConfig} from '@sanity/sdk-react'
22
import {Spinner, ThemeProvider} from '@sanity/ui'
33
import {buildTheme} from '@sanity/ui/theme'
4-
import {type JSX, Suspense} from 'react'
4+
import {type JSX, Suspense, useState} from 'react'
5+
import {registerSubscription, unregisterSubscription, createSubscriptionRequest} from '@sanity/sdk'
56

67
const theme = buildTheme({})
78

@@ -20,6 +21,77 @@ const devConfigs: SanityConfig[] = [
2021
},
2122
]
2223

24+
// SharedWorker test component
25+
function SharedWorkerTest() {
26+
const [subscriptionId, setSubscriptionId] = useState<string | null>(null)
27+
const [status, setStatus] = useState<string>('Ready to test')
28+
29+
const testSubscription = async () => {
30+
console.log('testSubscription')
31+
try {
32+
setStatus('Testing subscription...')
33+
34+
const subscription = createSubscriptionRequest({
35+
storeName: 'query',
36+
projectId: 'ppsg7ml5',
37+
dataset: 'test',
38+
params: {
39+
query: '*[_type == "movie"]',
40+
options: {},
41+
},
42+
appId: 'dashboard-app',
43+
})
44+
45+
const id = await registerSubscription(subscription)
46+
setSubscriptionId(id)
47+
setStatus(`Subscription registered: ${id}`)
48+
} catch (error) {
49+
setStatus(`Error: ${error instanceof Error ? error.message : String(error)}`)
50+
}
51+
}
52+
53+
const testUnsubscription = async () => {
54+
if (!subscriptionId) return
55+
56+
try {
57+
setStatus('Testing unsubscription...')
58+
59+
await unregisterSubscription(subscriptionId)
60+
setSubscriptionId(null)
61+
setStatus('Subscription unregistered successfully')
62+
} catch (error) {
63+
setStatus(`Error: ${error instanceof Error ? error.message : String(error)}`)
64+
}
65+
}
66+
67+
return (
68+
<div style={{padding: 12, borderBottom: '1px solid #eee'}}>
69+
<div>Dashboard (iframes sdk-app below)</div>
70+
<div style={{marginTop: 8, fontSize: '14px'}}>
71+
<div>SharedWorker Test:</div>
72+
<div style={{marginTop: 4}}>
73+
<button onClick={testSubscription} disabled={!!subscriptionId}>
74+
Test Subscription
75+
</button>
76+
{subscriptionId && (
77+
<button onClick={testUnsubscription} style={{marginLeft: 8}}>
78+
Test Unsubscription
79+
</button>
80+
)}
81+
</div>
82+
<div style={{marginTop: 4, fontFamily: 'monospace', fontSize: '12px'}}>
83+
Status: {status}
84+
</div>
85+
{subscriptionId && (
86+
<div style={{marginTop: 4, fontFamily: 'monospace', fontSize: '12px'}}>
87+
Active Subscription: {subscriptionId}
88+
</div>
89+
)}
90+
</div>
91+
</div>
92+
)
93+
}
94+
2395
export default function App(): JSX.Element {
2496
return (
2597
<ThemeProvider theme={theme}>
@@ -33,9 +105,7 @@ export default function App(): JSX.Element {
33105
flexDirection: 'column',
34106
}}
35107
>
36-
<div style={{padding: 12, borderBottom: '1px solid #eee'}}>
37-
Dashboard (iframes sdk-app below)
38-
</div>
108+
<SharedWorkerTest />
39109
<iframe
40110
title="sdk-app"
41111
src="http://localhost:3341/"

apps/dashboard/src/main.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
11
import {StrictMode} from 'react'
22
import {createRoot} from 'react-dom/client'
3+
import {getSdkWorker, addStatusListener, type WorkerStatus} from '@sanity/sdk'
4+
import sdkWorker from '@sanity/sdk/worker?worker&url'
35

46
import App from './App'
57

8+
// Initialize SharedWorker for subscription management
9+
async function initializeSharedWorker() {
10+
try {
11+
// Get the SDK worker instance - use direct URL
12+
const workerUrl = new URL(sdkWorker, import.meta.url).href
13+
14+
getSdkWorker(workerUrl)
15+
16+
// Add status listener for debugging
17+
addStatusListener((status: WorkerStatus) => {
18+
console.log('[Dashboard] Worker status changed:', status)
19+
})
20+
} catch (error) {
21+
console.warn('Failed to initialize SharedWorker:', error)
22+
// Fallback to local subscription management
23+
}
24+
}
25+
26+
// Initialize SharedWorker when the app starts
27+
initializeSharedWorker()
28+
629
createRoot(document.getElementById('root')!).render(
730
<StrictMode>
831
<App />

knip.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const baseConfig = {
8787
},
8888
project,
8989
entry: ['package.bundle.ts'],
90-
ignore: ['src/presence/bifurTransport.ts', 'src/presence/types.ts'],
90+
ignore: ['src/presence/bifurTransport.ts', 'src/presence/types.ts', 'src/_exports/worker.ts'],
9191
ignoreDependencies: ['@sanity/bifur-client', '@sanity/browserslist-config'],
9292
},
9393
},

packages/core/src/_exports/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@ export {
132132
export {getPerspectiveState} from '../releases/getPerspectiveState'
133133
export type {ReleaseDocument} from '../releases/releasesStore'
134134
export {getActiveReleasesState} from '../releases/releasesStore'
135+
export {
136+
addStatusListener,
137+
disconnectWorker,
138+
getSdkWorker,
139+
registerSubscription,
140+
sendMessage,
141+
unregisterSubscription,
142+
type WorkerStatus,
143+
} from '../sharedWorkerStore/sharedWorkerClient'
144+
export {type SharedWorkerStore, sharedWorkerStore} from '../sharedWorkerStore/sharedWorkerStore'
145+
export {
146+
type ActiveSubscription,
147+
type SharedWorkerStoreActions,
148+
type SharedWorkerStoreState,
149+
type SubscriptionRequest,
150+
} from '../sharedWorkerStore/types'
151+
export {
152+
areSubscriptionsEquivalent,
153+
createSubscriptionId,
154+
createSubscriptionRequest,
155+
groupSubscriptionsByParams,
156+
} from '../sharedWorkerStore/utils/subscriptionManager'
135157
export {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
136158
export {type Selector, type StateSource} from '../store/createStateSourceAction'
137159
export {getUsersKey, parseUsersKey} from '../users/reducers'
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/// <reference lib="webworker" />
2+
/* eslint-disable no-console */
3+
4+
/**
5+
* @internal
6+
* SharedWorker for managing subscriptions across SDK apps
7+
*/
8+
9+
import {sharedWorkerStore} from '../sharedWorkerStore/sharedWorkerStore'
10+
import {type SubscriptionRequest} from '../sharedWorkerStore/types'
11+
12+
declare const self: SharedWorkerGlobalScope
13+
14+
console.log('[SharedWorker] Worker script loaded')
15+
16+
// Handle new connections
17+
self.onconnect = (event: MessageEvent) => {
18+
const port = event.ports[0]
19+
20+
console.log('[SharedWorker] New connection established')
21+
22+
// Set up message handling for this port
23+
port.onmessage = async (e: MessageEvent) => {
24+
const {type, data} = e.data
25+
26+
console.log('[SharedWorker] Received message:', type, data)
27+
28+
try {
29+
switch (type) {
30+
case 'REGISTER_SUBSCRIPTION':
31+
handleRegisterSubscription(data, port)
32+
break
33+
case 'UNREGISTER_SUBSCRIPTION':
34+
handleUnregisterSubscription(data.subscriptionId, port)
35+
break
36+
case 'GET_SUBSCRIPTION_COUNT':
37+
handleGetSubscriptionCount(port)
38+
break
39+
case 'GET_ALL_SUBSCRIPTIONS':
40+
handleGetAllSubscriptions(port)
41+
break
42+
default:
43+
console.warn('[SharedWorker] Unknown message type:', type)
44+
port.postMessage({
45+
type: 'ERROR',
46+
data: {error: `Unknown message type: ${type}`},
47+
})
48+
}
49+
} catch (error) {
50+
console.error('[SharedWorker] Error handling message:', error)
51+
port.postMessage({
52+
type: 'ERROR',
53+
data: {error: (error as Error).message},
54+
})
55+
}
56+
}
57+
58+
// Start the port
59+
port.start()
60+
console.log('[SharedWorker] Port started, sending welcome message')
61+
port.postMessage({type: 'welcome'})
62+
}
63+
64+
/**
65+
* @internal
66+
* Handle the registration of a subscription
67+
* @param subscription - The subscription to register
68+
* @param port - The port to send the response to
69+
*/
70+
function handleRegisterSubscription(subscription: SubscriptionRequest, port: MessagePort): void {
71+
try {
72+
sharedWorkerStore.getState().registerSubscription(subscription)
73+
74+
// Send confirmation back to the client
75+
port.postMessage({
76+
type: 'SUBSCRIPTION_REGISTERED',
77+
data: {subscriptionId: subscription.subscriptionId},
78+
})
79+
80+
console.log('[SharedWorker] Registered subscription:', subscription.subscriptionId)
81+
} catch (error) {
82+
console.error('[SharedWorker] Failed to register subscription:', error)
83+
84+
// Send error back to the client
85+
port.postMessage({
86+
type: 'SUBSCRIPTION_ERROR',
87+
data: {error: (error as Error).message, subscriptionId: subscription.subscriptionId},
88+
})
89+
}
90+
}
91+
92+
/**
93+
* @internal
94+
* Handle the unregistration of a subscription
95+
* @param subscriptionId - The ID of the subscription to unregister
96+
* @param port - The port to send the response to
97+
*/
98+
function handleUnregisterSubscription(subscriptionId: string, port: MessagePort): void {
99+
try {
100+
sharedWorkerStore.getState().unregisterSubscription(subscriptionId)
101+
102+
// Send confirmation back to the client
103+
port.postMessage({
104+
type: 'SUBSCRIPTION_UNREGISTERED',
105+
data: {subscriptionId},
106+
})
107+
108+
console.log('[SharedWorker] Unregistered subscription:', subscriptionId)
109+
} catch (error) {
110+
console.error('[SharedWorker] Failed to unregister subscription:', error)
111+
112+
// Send error back to the client
113+
port.postMessage({
114+
type: 'SUBSCRIPTION_ERROR',
115+
data: {error: (error as Error).message, subscriptionId},
116+
})
117+
}
118+
}
119+
120+
function handleGetSubscriptionCount(port: MessagePort): void {
121+
try {
122+
const count = sharedWorkerStore.getState().getSubscriptionCount()
123+
124+
port.postMessage({
125+
type: 'SUBSCRIPTION_COUNT',
126+
data: {count},
127+
})
128+
} catch (error) {
129+
console.error('[SharedWorker] Failed to get subscription count:', error)
130+
131+
port.postMessage({
132+
type: 'SUBSCRIPTION_ERROR',
133+
data: {error: (error as Error).message},
134+
})
135+
}
136+
}
137+
138+
function handleGetAllSubscriptions(port: MessagePort): void {
139+
try {
140+
const subscriptions = sharedWorkerStore.getState().getAllSubscriptions()
141+
142+
port.postMessage({
143+
type: 'ALL_SUBSCRIPTIONS',
144+
data: {subscriptions},
145+
})
146+
} catch (error) {
147+
console.error('[SharedWorker] Failed to get all subscriptions:', error)
148+
149+
port.postMessage({
150+
type: 'SUBSCRIPTION_ERROR',
151+
data: {error: (error as Error).message},
152+
})
153+
}
154+
}
155+
156+
// Export for testing/development (this won't be used in the actual SharedWorker)
157+
/** @internal */
158+
export {handleRegisterSubscription, handleUnregisterSubscription}

0 commit comments

Comments
 (0)