11import * as automerge from '@automerge/automerge' ;
22import { uuid } from '@automerge/automerge' ;
3- import { type AutomergeUrl , type DocHandle , Repo } from '@automerge/automerge-repo' ;
43import { bytesToHex , hexToBytes } from '@noble/hashes/utils' ;
54import { useSelector as useSelectorStore } from '@xstate/store/react' ;
65import { Effect , Exit } from 'effect' ;
@@ -28,8 +27,6 @@ import { assertExhaustive } from './assertExhaustive.js';
2827import type { SpaceStorageEntry } from './store.js' ;
2928import { store } from './store.js' ;
3029
31- const hardcodedUrl = 'automerge:2JWupfYZBBm7s2NCy1VnvQa4Vdvf' as AutomergeUrl ;
32-
3330const decodeResponseMessage = Schema . decodeUnknownEither ( ResponseMessage ) ;
3431
3532type Props = {
@@ -63,8 +60,6 @@ const GraphFrameworkContext = createContext<{
6360 encryptionPublicKey : string ;
6461 } ;
6562 } ) => Promise < unknown > ;
66- repo : Repo ;
67- automergeHandle : DocHandle < unknown > ;
6863} > ( {
6964 invitations : [ ] ,
7065 createSpace : async ( ) => { } ,
@@ -73,59 +68,16 @@ const GraphFrameworkContext = createContext<{
7368 acceptInvitation : async ( ) => { } ,
7469 subscribeToSpace : ( ) => { } ,
7570 inviteToSpace : async ( ) => { } ,
76- // @ts -expect-error repo is always set
77- repo : undefined ,
78- // @ts -expect-error automergeHandle is always set
79- automergeHandle : undefined ,
8071} ) ;
8172
8273export function GraphFramework ( { children, accountId } : Props ) {
8374 const [ websocketConnection , setWebsocketConnection ] = useState < WebSocket > ( ) ;
84- const [ repo ] = useState < Repo > ( ( ) => new Repo ( { } ) ) ;
85- const [ automergeHandle ] = useState < DocHandle < unknown > > ( ( ) => repo . find ( hardcodedUrl ) ) ;
8675 const spaces = useSelectorStore ( store , ( state ) => state . context . spaces ) ;
8776 const invitations = useSelectorStore ( store , ( state ) => state . context . invitations ) ;
8877 // Create a stable WebSocket connection that only depends on accountId
8978 useEffect ( ( ) => {
9079 const websocketConnection = new WebSocket ( `ws://localhost:3030/?accountId=${ accountId } ` ) ;
9180
92- const docHandle = automergeHandle ;
93- // set it to ready to interact with the document
94- docHandle . doneLoading ( ) ;
95-
96- docHandle . on ( 'change' , ( result ) => {
97- const lastLocalChange = automerge . getLastLocalChange ( result . doc ) ;
98- if ( ! lastLocalChange ) {
99- return ;
100- }
101-
102- try {
103- const storeState = store . getSnapshot ( ) ;
104- const space = storeState . context . spaces [ 0 ] ;
105-
106- const ephemeralId = uuid ( ) ;
107-
108- const nonceAndCiphertext = encryptMessage ( {
109- message : lastLocalChange ,
110- secretKey : hexToBytes ( space . keys [ 0 ] . key ) ,
111- } ) ;
112-
113- const messageToSend : RequestCreateUpdate = {
114- type : 'create-update' ,
115- ephemeralId,
116- update : nonceAndCiphertext ,
117- spaceId : space . id ,
118- } ;
119- websocketConnection . send ( serialize ( messageToSend ) ) ;
120- } catch ( error ) {
121- console . error ( 'Error sending message' , error ) ;
122- }
123- } ) ;
124-
125- store . send ( {
126- type : 'setAutomergeDocumentId' ,
127- automergeDocumentId : docHandle . url . slice ( 10 ) ,
128- } ) ;
12981 setWebsocketConnection ( websocketConnection ) ;
13082
13183 const onOpen = ( ) => {
@@ -150,10 +102,9 @@ export function GraphFramework({ children, accountId }: Props) {
150102 websocketConnection . removeEventListener ( 'close' , onClose ) ;
151103 websocketConnection . close ( ) ;
152104 } ;
153- } , [ accountId , automergeHandle ] ) ; // Only recreate when accountId changes
105+ } , [ accountId ] ) ; // Only recreate when accountId changes
154106
155107 // Handle WebSocket messages in a separate effect
156- // biome-ignore lint/correctness/useExhaustiveDependencies: automergeHandle is a mutable object
157108 useEffect ( ( ) => {
158109 if ( ! websocketConnection ) return ;
159110
@@ -189,7 +140,7 @@ export function GraphFramework({ children, accountId }: Props) {
189140
190141 const newState = state as SpaceState ;
191142
192- const storeState = store . getSnapshot ( ) ;
143+ let storeState = store . getSnapshot ( ) ;
193144
194145 const keys = response . keyBoxes . map ( ( keyBox ) => {
195146 const key = decryptKey ( {
@@ -210,6 +161,13 @@ export function GraphFramework({ children, accountId }: Props) {
210161 keys,
211162 } ) ;
212163
164+ storeState = store . getSnapshot ( ) ;
165+ const automergeDocHandle = storeState . context . spaces . find ( ( s ) => s . id === response . id ) ?. automergeDocHandle ;
166+ if ( ! automergeDocHandle ) {
167+ console . error ( 'No automergeDocHandle found' , response . id ) ;
168+ return ;
169+ }
170+
213171 if ( response . updates ) {
214172 const updates = response . updates ?. updates . map ( ( update ) => {
215173 return decryptMessage ( {
@@ -219,11 +177,7 @@ export function GraphFramework({ children, accountId }: Props) {
219177 } ) ;
220178
221179 for ( const update of updates ) {
222- if ( ! automergeHandle ) {
223- return ;
224- }
225-
226- automergeHandle . update ( ( existingDoc ) => {
180+ automergeDocHandle . update ( ( existingDoc ) => {
227181 const [ newDoc ] = automerge . applyChanges ( existingDoc , [ update ] ) ;
228182 return newDoc ;
229183 } ) ;
@@ -236,6 +190,36 @@ export function GraphFramework({ children, accountId }: Props) {
236190 lastUpdateClock : response . updates ?. lastUpdateClock ,
237191 } ) ;
238192 }
193+
194+ automergeDocHandle . on ( 'change' , ( result ) => {
195+ const lastLocalChange = automerge . getLastLocalChange ( result . doc ) ;
196+ if ( ! lastLocalChange ) {
197+ return ;
198+ }
199+
200+ try {
201+ const storeState = store . getSnapshot ( ) ;
202+ const space = storeState . context . spaces [ 0 ] ;
203+
204+ const ephemeralId = uuid ( ) ;
205+
206+ const nonceAndCiphertext = encryptMessage ( {
207+ message : lastLocalChange ,
208+ secretKey : hexToBytes ( space . keys [ 0 ] . key ) ,
209+ } ) ;
210+
211+ const messageToSend : RequestCreateUpdate = {
212+ type : 'create-update' ,
213+ ephemeralId,
214+ update : nonceAndCiphertext ,
215+ spaceId : space . id ,
216+ } ;
217+ websocketConnection . send ( serialize ( messageToSend ) ) ;
218+ } catch ( error ) {
219+ console . error ( 'Error sending message' , error ) ;
220+ }
221+ } ) ;
222+
239223 break ;
240224 }
241225 case 'space-event' : {
@@ -298,7 +282,7 @@ export function GraphFramework({ children, accountId }: Props) {
298282 } ) ;
299283 } ) ;
300284
301- automergeHandle ?. update ( ( existingDoc ) => {
285+ space ?. automergeDocHandle ?. update ( ( existingDoc ) => {
302286 const [ newDoc ] = automerge . applyChanges ( existingDoc , automergeUpdates ) ;
303287 return newDoc ;
304288 } ) ;
@@ -488,8 +472,6 @@ export function GraphFramework({ children, accountId }: Props) {
488472 acceptInvitation : acceptInvitationForContext ,
489473 subscribeToSpace,
490474 inviteToSpace,
491- repo,
492- automergeHandle,
493475 } }
494476 >
495477 { children }
0 commit comments