@@ -6,6 +6,9 @@ var ShareDBError = require('./error');
6
6
7
7
var ERROR_CODE = ShareDBError . CODES ;
8
8
9
+ /** @typedef {import('./backend') } Backend */
10
+ /** @typedef {import('./backend').ServerPlugin<unknown, unknown, unknown> } ServerPlugin */
11
+
9
12
/**
10
13
* Agent deserializes the wire protocol messages received from the stream and
11
14
* calls the corresponding functions on its Agent. It uses the return values
@@ -16,6 +19,7 @@ var ERROR_CODE = ShareDBError.CODES;
16
19
* @param {Duplex } stream connection to a client
17
20
*/
18
21
function Agent ( backend , stream ) {
22
+ /** @type {Backend } */
19
23
this . backend = backend ;
20
24
this . stream = stream ;
21
25
@@ -32,8 +36,24 @@ function Agent(backend, stream) {
32
36
// map of collection -> id -> stream
33
37
this . subscribedDocs = { } ;
34
38
35
- // Map from queryId -> emitter
36
- this . subscribedQueries = { } ;
39
+ /**
40
+ * Map of action name (`a` field in requests) to plugin
41
+ * @type {{ [action: string]: ServerPlugin } }
42
+ */
43
+ var actionToPlugin = this . actionToPlugin = { } ;
44
+ // Map of plugin name -> plugin's agent state
45
+ this . pluginStates = { } ;
46
+ for ( var i = 0 ; i < backend . plugins . length ; i ++ ) {
47
+ var plugin = backend . plugins [ i ] ;
48
+ for ( var action in plugin . requestHandlers ) {
49
+ if ( actionToPlugin [ action ] ) {
50
+ throw new Error ( 'Action ' + action + ' in plugin ' + plugin . name +
51
+ ' conflicts with plugin ' + actionToPlugin [ action ] . name ) ;
52
+ }
53
+ actionToPlugin [ action ] = plugin ;
54
+ }
55
+ this . pluginStates [ plugin . name ] = plugin . createAgentState ( ) ;
56
+ }
37
57
38
58
// Track which documents are subscribed to presence by the client. This is a
39
59
// map of channel -> stream
@@ -167,43 +187,6 @@ Agent.prototype._subscribeToPresenceStream = function(channel, stream) {
167
187
} ) ;
168
188
} ;
169
189
170
- Agent . prototype . _subscribeToQuery = function ( emitter , queryId , collection , query ) {
171
- var previous = this . subscribedQueries [ queryId ] ;
172
- if ( previous ) previous . destroy ( ) ;
173
- this . subscribedQueries [ queryId ] = emitter ;
174
-
175
- var agent = this ;
176
- emitter . onExtra = function ( extra ) {
177
- agent . send ( { a : 'q' , id : queryId , extra : extra } ) ;
178
- } ;
179
-
180
- emitter . onDiff = function ( diff ) {
181
- for ( var i = 0 ; i < diff . length ; i ++ ) {
182
- var item = diff [ i ] ;
183
- if ( item . type === 'insert' ) {
184
- item . values = getResultsData ( item . values ) ;
185
- }
186
- }
187
- // Consider stripping the collection out of the data we send here
188
- // if it matches the query's collection.
189
- agent . send ( { a : 'q' , id : queryId , diff : diff } ) ;
190
- } ;
191
-
192
- emitter . onError = function ( err ) {
193
- // Log then silently ignore errors in a subscription stream, since these
194
- // may not be the client's fault, and they were not the result of a
195
- // direct request by the client
196
- logger . error ( 'Query subscription stream error' , collection , query , err ) ;
197
- } ;
198
-
199
- emitter . onOp = function ( op ) {
200
- var id = op . d ;
201
- agent . _onOp ( collection , id , op ) ;
202
- } ;
203
-
204
- emitter . _open ( ) ;
205
- } ;
206
-
207
190
Agent . prototype . _onOp = function ( collection , id , op ) {
208
191
if ( this . _isOwnOp ( collection , op ) ) return ;
209
192
@@ -379,19 +362,22 @@ Agent.prototype._checkRequest = function(request) {
379
362
// Handle an incoming message from the client
380
363
Agent . prototype . _handleMessage = function ( request , callback ) {
381
364
try {
365
+ var plugin = this . actionToPlugin [ request . a ] ;
366
+
382
367
var errMessage = this . _checkRequest ( request ) ;
383
368
if ( errMessage ) return callback ( new ShareDBError ( ERROR_CODE . ERR_MESSAGE_BADLY_FORMED , errMessage ) ) ;
369
+ if ( plugin ) {
370
+ try {
371
+ plugin . checkRequest ( request ) ;
372
+ } catch ( err ) {
373
+ return callback ( new ShareDBError ( ERROR_CODE . ERR_MESSAGE_BADLY_FORMED , err . message ) ) ;
374
+ }
375
+ }
384
376
385
377
switch ( request . a ) {
386
378
case 'hs' :
387
379
if ( request . id ) this . src = request . id ;
388
380
return callback ( null , this . _initMessage ( 'hs' ) ) ;
389
- case 'qf' :
390
- return this . _queryFetch ( request . id , request . c , request . q , getQueryOptions ( request ) , callback ) ;
391
- case 'qs' :
392
- return this . _querySubscribe ( request . id , request . c , request . q , getQueryOptions ( request ) , callback ) ;
393
- case 'qu' :
394
- return this . _queryUnsubscribe ( request . id , callback ) ;
395
381
case 'bf' :
396
382
return this . _fetchBulk ( request . c , request . b , callback ) ;
397
383
case 'bs' :
@@ -435,109 +421,23 @@ Agent.prototype._handleMessage = function(request, callback) {
435
421
case 'pu' :
436
422
return this . _unsubscribePresence ( request . ch , request . seq , callback ) ;
437
423
default :
438
- callback ( new ShareDBError ( ERROR_CODE . ERR_MESSAGE_BADLY_FORMED , 'Invalid or unknown message' ) ) ;
439
- }
440
- } catch ( err ) {
441
- callback ( err ) ;
442
- }
443
- } ;
444
- function getQueryOptions ( request ) {
445
- var results = request . r ;
446
- var ids ;
447
- var fetch ;
448
- var fetchOps ;
449
- if ( results ) {
450
- ids = [ ] ;
451
- for ( var i = 0 ; i < results . length ; i ++ ) {
452
- var result = results [ i ] ;
453
- var id = result [ 0 ] ;
454
- var version = result [ 1 ] ;
455
- ids . push ( id ) ;
456
- if ( version == null ) {
457
- if ( fetch ) {
458
- fetch . push ( id ) ;
424
+ if ( plugin ) {
425
+ return plugin . requestHandlers [ request . a ] ( request , {
426
+ agent : this ,
427
+ backend : this . backend ,
428
+ agentState : this . pluginStates [ plugin . name ]
429
+ } , callback ) ;
459
430
} else {
460
- fetch = [ id ] ;
431
+ callback (
432
+ new ShareDBError ( ERROR_CODE . ERR_MESSAGE_BADLY_FORMED , 'Invalid or unknown message' )
433
+ ) ;
461
434
}
462
- } else {
463
- if ( ! fetchOps ) fetchOps = { } ;
464
- fetchOps [ id ] = version ;
465
- }
466
435
}
436
+ } catch ( err ) {
437
+ callback ( err ) ;
467
438
}
468
- var options = request . o || { } ;
469
- options . ids = ids ;
470
- options . fetch = fetch ;
471
- options . fetchOps = fetchOps ;
472
- return options ;
473
- }
474
-
475
- Agent . prototype . _queryFetch = function ( queryId , collection , query , options , callback ) {
476
- // Fetch the results of a query once
477
- this . backend . queryFetch ( this , collection , query , options , function ( err , results , extra ) {
478
- if ( err ) return callback ( err ) ;
479
- var message = {
480
- data : getResultsData ( results ) ,
481
- extra : extra
482
- } ;
483
- callback ( null , message ) ;
484
- } ) ;
485
- } ;
486
-
487
- Agent . prototype . _querySubscribe = function ( queryId , collection , query , options , callback ) {
488
- // Subscribe to a query. The client is sent the query results and its
489
- // notified whenever there's a change
490
- var agent = this ;
491
- var wait = 1 ;
492
- var message ;
493
- function finish ( err ) {
494
- if ( err ) return callback ( err ) ;
495
- if ( -- wait ) return ;
496
- callback ( null , message ) ;
497
- }
498
- if ( options . fetch ) {
499
- wait ++ ;
500
- this . backend . fetchBulk ( this , collection , options . fetch , function ( err , snapshotMap ) {
501
- if ( err ) return finish ( err ) ;
502
- message = getMapResult ( snapshotMap ) ;
503
- finish ( ) ;
504
- } ) ;
505
- }
506
- if ( options . fetchOps ) {
507
- wait ++ ;
508
- this . _fetchBulkOps ( collection , options . fetchOps , finish ) ;
509
- }
510
- this . backend . querySubscribe ( this , collection , query , options , function ( err , emitter , results , extra ) {
511
- if ( err ) return finish ( err ) ;
512
- if ( this . closed ) return emitter . destroy ( ) ;
513
-
514
- agent . _subscribeToQuery ( emitter , queryId , collection , query ) ;
515
- // No results are returned when ids are passed in as an option. Instead,
516
- // want to re-poll the entire query once we've established listeners to
517
- // emit any diff in results
518
- if ( ! results ) {
519
- emitter . queryPoll ( finish ) ;
520
- return ;
521
- }
522
- message = {
523
- data : getResultsData ( results ) ,
524
- extra : extra
525
- } ;
526
- finish ( ) ;
527
- } ) ;
528
439
} ;
529
440
530
- function getResultsData ( results ) {
531
- var items = [ ] ;
532
- for ( var i = 0 ; i < results . length ; i ++ ) {
533
- var result = results [ i ] ;
534
- var item = getSnapshotData ( result ) ;
535
- item . d = result . id ;
536
- items . push ( item ) ;
537
- }
538
- return items ;
539
- }
540
-
541
441
function getMapResult ( snapshotMap ) {
542
442
var data = { } ;
543
443
for ( var id in snapshotMap ) {
@@ -553,6 +453,8 @@ function getMapResult(snapshotMap) {
553
453
}
554
454
return { data : data } ;
555
455
}
456
+ // Exported for use in core plugins
457
+ Agent . _getMapResult = getMapResult ;
556
458
557
459
function getSnapshotData ( snapshot ) {
558
460
var data = {
@@ -564,15 +466,8 @@ function getSnapshotData(snapshot) {
564
466
}
565
467
return data ;
566
468
}
567
-
568
- Agent . prototype . _queryUnsubscribe = function ( queryId , callback ) {
569
- var emitter = this . subscribedQueries [ queryId ] ;
570
- if ( emitter ) {
571
- emitter . destroy ( ) ;
572
- delete this . subscribedQueries [ queryId ] ;
573
- }
574
- process . nextTick ( callback ) ;
575
- } ;
469
+ // Exported for use in core plugins
470
+ Agent . _getSnapshotData = getSnapshotData ;
576
471
577
472
Agent . prototype . _fetch = function ( collection , id , version , callback ) {
578
473
if ( version == null ) {
0 commit comments