@@ -12,25 +12,33 @@ export { receiveCommitTransaction, getVersion, Commit, type CommitJSON, type Nod
1212
1313export { collab , collabKey } from "./plugin" ;
1414
15+ export interface CommitsListener {
16+ listen : (
17+ version : number ,
18+ options ?: { signal ?: AbortSignal } ,
19+ ) => AsyncIterableIterator < CommitJSON [ ] > ;
20+ }
21+
1522export interface CollabClientConfig {
1623 sendCommit : ( commit : Commit ) => Promise < void > ;
17- getCommits : ( version : number , options ?: { signal ?: AbortSignal } ) => Promise < CommitJSON [ ] > ;
24+ listener : CommitsListener ;
1825 receiveCommits : ( commits : Commit [ ] ) => void ;
1926}
2027
2128export class CollabClient {
2229 private sending : null | string = null ;
2330 private version : number | undefined = undefined ;
2431 private seen = new Set < string > ( ) ;
32+ private controller = new AbortController ( ) ;
2533
2634 private sendCommit : CollabClientConfig [ "sendCommit" ] ;
27- private getCommits : CollabClientConfig [ "getCommits " ] ;
35+ private listener : CollabClientConfig [ "listener " ] ;
2836 private receiveCommits : CollabClientConfig [ "receiveCommits" ] ;
2937
3038 constructor ( config : CollabClientConfig ) {
3139 this . sendCommit = config . sendCommit ;
3240 this . receiveCommits = config . receiveCommits ;
33- this . getCommits = config . getCommits ;
41+ this . listener = config . listener ;
3442 }
3543
3644 async send ( editorState : EditorState ) {
@@ -52,33 +60,34 @@ export class CollabClient {
5260 }
5361 }
5462
55- async listen ( editorState : EditorState , signal : AbortSignal ) {
63+ update ( config : Partial < Omit < CollabClientConfig , "listener" > > ) {
64+ if ( config . sendCommit ) this . sendCommit = config . sendCommit ;
65+ if ( config . receiveCommits ) this . receiveCommits = config . receiveCommits ;
66+ }
67+
68+ async listen ( editorState : EditorState , signal ?: AbortSignal ) {
5669 this . version = getVersion ( editorState ) ;
5770
5871 if ( this . version === undefined ) {
5972 throw new Error ( "EditorState is missing the collab plugin, unable to listen for changes" ) ;
6073 }
6174
62- while ( ! signal . aborted ) {
63- try {
64- const commitJSONs = await this . getCommits ( this . version , { signal } ) ;
65- const commits = commitJSONs . map ( ( json ) => Commit . FromJSON ( editorState . schema , json ) ) ;
75+ const getCommitsSignal = AbortSignal . any ( [ ...( signal ? [ signal ] : [ ] ) , this . controller . signal ] ) ;
6676
67- // Ensure that we don't process the same commit multiple times
68- const newCommits = commits . filter ( ( commit ) => ! this . seen . has ( commit . ref ) ) ;
69- const lastCommit = newCommits [ newCommits . length - 1 ] ;
70- if ( ! lastCommit ) continue ;
71- this . version = lastCommit . version ;
72- newCommits . forEach ( ( commit ) => this . seen . add ( commit . ref ) ) ;
77+ for await ( const commitJSONs of this . listener . listen ( this . version , {
78+ signal : getCommitsSignal ,
79+ } ) ) {
80+ if ( getCommitsSignal . aborted ) break ;
81+ const commits = commitJSONs . map ( ( json ) => Commit . FromJSON ( editorState . schema , json ) ) ;
7382
74- this . receiveCommits ( newCommits ) ;
75- } catch ( e ) {
76- // TODO: Implement a backoff strategy
77- console . error ( e ) ;
78- await new Promise < void > ( ( resolve ) => {
79- setTimeout ( ( ) => resolve ( ) , 3_000 ) ;
80- } ) ;
81- }
83+ // Ensure that we don't process the same commit multiple times
84+ const newCommits = commits . filter ( ( commit ) => ! this . seen . has ( commit . ref ) ) ;
85+ const lastCommit = newCommits [ newCommits . length - 1 ] ;
86+ if ( ! lastCommit ) continue ;
87+ this . version = lastCommit . version ;
88+ newCommits . forEach ( ( commit ) => this . seen . add ( commit . ref ) ) ;
89+
90+ this . receiveCommits ( newCommits ) ;
8291 }
8392 }
8493}
@@ -99,22 +108,39 @@ export class LongPollListener {
99108 ) {
100109 this . headers = options . headers ?? { } ;
101110 this . fetch = options . fetch ?? globalThis . fetch . bind ( globalThis ) ;
102- this . getCommits = this . getCommits . bind ( this ) ;
103111 }
104112
105- async getCommits ( version : number ) {
106- const url = new URL ( this . url ) ;
107- url . searchParams . append ( "version" , version . toString ( ) ) ;
113+ update ( headers : HeadersInit ) {
114+ this . headers = headers ;
115+ }
116+
117+ async * listen ( version : number , options : { signal ?: AbortSignal } = { } ) {
118+ while ( ! options ?. signal || ! options . signal . aborted ) {
119+ const url = new URL ( this . url ) ;
120+ url . searchParams . append ( "version" , version . toString ( ) ) ;
108121
109- const response = await this . fetch ( url , {
110- headers : this . headers ,
111- } ) ;
122+ try {
123+ const response = await this . fetch ( url , {
124+ headers : this . headers ,
125+ ...( options ?. signal && { signal : options . signal } ) ,
126+ } ) ;
112127
113- if ( ! response . ok ) {
114- throw new Error ( `Failed to get commits. ${ response . status } : ${ response . statusText } ` ) ;
115- }
128+ if ( ! response . ok ) {
129+ throw new Error ( `Failed to get commits. ${ response . status } : ${ response . statusText } ` ) ;
130+ }
131+
132+ const commitJSONs = ( await response . json ( ) ) as CommitJSON [ ] ;
133+ yield commitJSONs ;
134+ } catch ( e ) {
135+ console . error ( e ) ;
116136
117- const commitJSONs = ( await response . json ( ) ) as CommitJSON [ ] ;
118- return commitJSONs ;
137+ if ( options . signal ?. aborted ) return ;
138+
139+ // TODO: Implement a backoff strategy
140+ await new Promise < void > ( ( resolve ) => {
141+ setTimeout ( ( ) => resolve ( ) , 3_000 ) ;
142+ } ) ;
143+ }
144+ }
119145 }
120146}
0 commit comments