11import { useLocation } from 'wouter' ;
2- import { useEffect , useRef , useState } from 'react' ;
2+ import { useEffect , useMemo , useRef , useState } from 'react' ;
33import { Editor , createEditor } from 'slate' ;
44import { withReact } from 'slate-react' ;
55import ReconnectingWebSocket from 'reconnecting-websocket' ;
@@ -10,22 +10,36 @@ import { LoroDoc } from 'loro-crdt';
1010
1111enum MessageSyncType {
1212 Change = 1 ,
13- ChangeBacklogComplete = 2 ,
14- FullDoc = 3 ,
13+ BacklogComplete = 2 ,
14+ }
15+
16+ function int_from_32bit_big_endian ( msg_data : Uint8Array , idx : number ) : number {
17+ let to_return = msg_data [ idx + 0 ] ;
18+ to_return = ( to_return << 8 ) + msg_data [ idx + 1 ] ;
19+ to_return = ( to_return << 8 ) + msg_data [ idx + 2 ] ;
20+ to_return = ( to_return << 8 ) + msg_data [ idx + 3 ] ;
21+ return to_return ;
1522}
1623
1724export function useAutomergeWebsocketEditor (
1825 url : string ,
1926 { onInitialSyncComplete } : { onInitialSyncComplete : ( editor ?: Editor ) => void } ,
20- ) : [ Editor ? , Paragraph [ ] ?] {
27+ ) : [ Editor , Paragraph [ ] ?] {
2128 const debug = useDebugMode ( ) ;
22- const [ editorAndInitialValue , setEditorAndInitialValue ] = useState < null | {
23- editor : Editor ;
24- initialValue : Paragraph [ ] ;
25- } > ( null ) ;
26- const editorRef = useRef < undefined | Editor > ( ) ;
27- if ( editorRef . current !== editorAndInitialValue ?. editor )
28- editorRef . current = editorAndInitialValue ?. editor ;
29+ const [ initialValue , setInitialValue ] = useState < null | Paragraph [ ] > ( null ) ;
30+ const editor = useMemo ( ( ) => {
31+ const doc = new LoroDoc ( ) ;
32+ const baseEditor = createEditor ( ) ;
33+ const editorWithReact = withReact ( baseEditor ) ;
34+
35+ doc . subscribeLocalUpdates ( sendDocChange ) ;
36+
37+ const editor = withLoroDoc ( editorWithReact , doc ) ;
38+ editor . _doc = doc ;
39+
40+ return editor ;
41+ } , [ url ] ) ;
42+
2943 const wsRef = useRef < ReconnectingWebSocket | null > ( null ) ;
3044
3145 function sendDocChange ( change : Uint8Array ) {
@@ -37,97 +51,60 @@ export function useAutomergeWebsocketEditor(
3751 useEffect ( ( ) => {
3852 const ws = new ReconnectingWebSocket ( url , [ ] , { debug } ) ;
3953
40- let bytesReceived = 0 ;
4154 console . time ( 'initialSync' ) ;
4255
43- const createNewEditor = ( doc : LoroDoc ) => {
44- const baseEditor = createEditor ( ) ;
45- const editorWithReact = withReact ( baseEditor ) ;
46-
47- doc . subscribeLocalUpdates ( sendDocChange )
48- // editor.addDocChangeListener(sendDocChange);
49-
50- const editor = withLoroDoc ( editorWithReact , doc ) ;
51- editor . doc = doc ;
52-
53- onInitialSyncComplete ( editor ) ;
54-
55- setEditorAndInitialValue ( ( oldValue ) => {
56- // oldValue?.editor.removeDocChangeListener(sendDocChange);
57- // const initialValue =
58- // doc.children !== undefined
59- // ? JSON.parse(JSON.stringify(migratedDoc.children))
60- // : [];
61- return { editor : editor , initialValue : editor . doc . toJSON ( ) . root . children } ;
62- } ) ;
63- } ;
64-
65- const onMessage = async ( event : MessageEvent ) => {
66- let msg_data = new Uint8Array ( await event . data . arrayBuffer ( ) ) ;
67- bytesReceived += msg_data . length ;
68- const updates = [ ] ;
69- let idx = 0 ;
70- let backlogComplete = false ;
71- let fullDoc = false ;
72- console . log ( "message" , msg_data )
73- while ( idx < msg_data . length ) {
74- const msg_type = msg_data [ idx ] ;
75- idx += 1 ;
76- if ( ( msg_type === MessageSyncType . Change ) || ( msg_type === MessageSyncType . FullDoc ) ) {
77- let msg_len = msg_data [ idx + 0 ] ;
78- msg_len = ( msg_len << 8 ) + msg_data [ idx + 1 ] ;
79- msg_len = ( msg_len << 8 ) + msg_data [ idx + 2 ] ;
80- msg_len = ( msg_len << 8 ) + msg_data [ idx + 3 ] ;
81-
82- // const msg_len = ((((msg_data[idx + 0] << 8 + msg_data[idx + 1]) << 8) + msg_data[idx + 2]) << 8) + msg_data[idx + 3];
83- console . log ( msg_len , msg_data [ idx + 0 ] , msg_data [ idx + 1 ] , msg_data [ idx + 2 ] , msg_data [ idx + 3 ] )
84- idx += 4 ;
85- updates . push ( msg_data . slice ( idx , idx + msg_len ) )
86- idx += msg_len
87-
88- // if (
89- // !editorRef.current ||
90- // Automerge.decodeChange(msg).actor == Automerge.getActorId(editorRef.current.doc)
91- // )
92- // return;
93-
94- // HistoryEditor.withoutSaving(editorRef.current, () => {
95- // editorRef.current?.setDoc(newDoc);
96- // });
97-
98- } else if ( msg_type === MessageSyncType . ChangeBacklogComplete ) {
99- backlogComplete = true ;
100- console . info ( 'backlog complete' ) ;
101- break ;
102- } else if ( msg_type === MessageSyncType . FullDoc ) {
103- fullDoc = true ;
56+ const updates = [ ] ;
57+ let initialSyncDone = false ;
58+
59+ const generator = messageGenerator ( ) ;
60+ generator . next ( ) ;
61+
62+ async function * messageGenerator ( ) : AsyncGenerator < void , void , Message > {
63+ while ( true ) {
64+ const message = yield ;
65+ if ( message ) {
66+ const msg_data = new Uint8Array ( await message . arrayBuffer ( ) ) ;
67+ const msg_type = msg_data [ 0 ] ;
68+
69+ console . log ( msg_data ) ;
70+ if ( msg_type === MessageSyncType . Change ) {
71+ let idx = 1 ;
72+ while ( idx < msg_data . length ) {
73+ const msg_len = int_from_32bit_big_endian ( msg_data , idx ) ;
74+ console . log ( msg_len ) ;
75+ idx += 4 ;
76+ updates . push ( msg_data . slice ( idx , idx + msg_len ) ) ;
77+ idx += msg_len ;
78+ }
79+ if ( initialSyncDone ) {
80+ console . time ( 'importBatch' ) ;
81+ console . log ( `updates:` , updates ) ;
82+ console . log ( editor . _doc . importBatch ( updates ) ) ;
83+ console . timeEnd ( 'importBatch' ) ;
84+ }
85+ } else if ( msg_type === MessageSyncType . BacklogComplete ) {
86+ console . time ( 'importBatch' ) ;
87+ console . log ( `updates:` , updates ) ;
88+ console . log ( editor . _doc . importBatch ( updates ) ) ;
89+ console . timeEnd ( 'importBatch' ) ;
90+ editor . onInitialSyncComplete ( ) ;
91+ setInitialValue ( editor . _doc . getMap ( 'root' ) . get ( 'children' ) . toJSON ( ) ) ;
92+ console . timeEnd ( 'initialSync' ) ;
93+ onInitialSyncComplete ( editor ) ;
94+ console . info ( 'backlog complete' ) ;
95+ initialSyncDone = true ;
96+ }
10497 }
10598 }
99+ }
106100
107- // TODO(robin): HACK
108- if ( ( fullDoc || ! editorRef . current ) && updates . length > 0 ) {
109- const doc = new LoroDoc ( )
110- console . time ( 'importBatch' ) ;
111- console . log ( updates )
112- console . log ( doc . importBatch ( updates ) ) ;
113- console . timeEnd ( 'importBatch' ) ;
114-
115- createNewEditor ( doc ) ;
116- } else {
117- if ( updates . length > 0 ) {
118- console . time ( 'importBatch' ) ;
119- editorRef . current ?. _doc . importBatch ( updates ) ;
120- console . timeEnd ( 'importBatch' ) ;
121- }
122- }
123-
124- if ( backlogComplete ) {
125- onInitialSyncComplete ( editorRef . current ) ;
126- }
101+ const onMessage = async ( event : MessageEvent ) => {
102+ await generator . next ( event . data ) ;
127103 } ;
128104 ws . addEventListener ( 'message' , ( msg ) => {
129105 onMessage ( msg ) . catch ( ( e ) => {
130- alert ( `error while loading automerge message occured: ${ e } ` ) ;
106+ console . log ( `error while loading sync message occured` , e ) ;
107+ alert ( `error while loading sync message occured: ${ e } ` ) ;
131108 navigate ( '/' ) ;
132109 } ) ;
133110 } ) ;
@@ -138,7 +115,7 @@ export function useAutomergeWebsocketEditor(
138115 wsRef . current = null ;
139116 ws . close ( ) ;
140117 } ;
141- } , [ url , setEditorAndInitialValue ] ) ;
118+ } , [ url ] ) ;
142119
143- return [ editorAndInitialValue ?. editor , editorAndInitialValue ?. initialValue ] ;
120+ return [ editor , initialValue ] ;
144121}
0 commit comments