@@ -27,8 +27,12 @@ function Agent(backend, stream) {
27
27
this . subscribedQueries = { } ;
28
28
29
29
// Track which documents are subscribed to presence by the client. This is a
30
- // map of collection -> id -> stream
30
+ // map of channel -> stream
31
31
this . subscribedPresences = { } ;
32
+ // Highest seq received for a subscription request. Any seq lower than this
33
+ // value is stale, and should be ignored. Used for keeping the subscription
34
+ // state in sync with the client's desired state
35
+ this . presenceSubscriptionSeq = 0 ;
32
36
33
37
// We need to track this manually to make sure we don't reply to messages
34
38
// after the stream was closed.
@@ -78,12 +82,8 @@ Agent.prototype._cleanup = function() {
78
82
}
79
83
this . subscribedDocs = { } ;
80
84
81
- for ( var collection in this . subscribedPresences ) {
82
- var streams = this . subscribedPresences [ collection ] ;
83
- for ( var id in streams ) {
84
- var stream = streams [ id ] ;
85
- stream . destroy ( ) ;
86
- }
85
+ for ( var channel in this . subscribedPresences ) {
86
+ this . subscribedPresences [ channel ] . destroy ( ) ;
87
87
}
88
88
this . subscribedPresences = { } ;
89
89
@@ -130,22 +130,19 @@ Agent.prototype._subscribeToStream = function(collection, id, stream) {
130
130
} ) ;
131
131
} ;
132
132
133
- Agent . prototype . _subscribeToPresenceStream = function ( collection , id , stream ) {
133
+ Agent . prototype . _subscribeToPresenceStream = function ( channel , stream ) {
134
134
if ( this . closed ) return stream . destroy ( ) ;
135
135
136
136
stream . on ( 'data' , function ( data ) {
137
137
if ( data . error ) {
138
- logger . error ( 'Presence subscription stream error' , collection , id , data . error ) ;
138
+ logger . error ( 'Presence subscription stream error' , channel , data . error ) ;
139
139
}
140
140
this . _handlePresenceData ( data ) ;
141
141
} . bind ( this ) ) ;
142
142
143
143
stream . on ( 'end' , function ( ) {
144
- var streams = this . subscribedPresences [ collection ] ;
145
- if ( ! streams || ! streams [ id ] !== stream ) return ;
146
- delete streams [ id ] ;
147
- if ( util . hasKeys ( streams ) ) return ;
148
- delete agent . subscribedPresences [ collection ] ;
144
+ if ( this . subscribedPresences [ channel ] !== stream ) return ;
145
+ delete this . subscribedPresences [ channel ] ;
149
146
} . bind ( this ) ) ;
150
147
} ;
151
148
@@ -394,10 +391,14 @@ Agent.prototype._handleMessage = function(request, callback) {
394
391
return this . _fetchSnapshotByTimestamp ( request . c , request . d , request . ts , callback ) ;
395
392
case 'p' :
396
393
var presence = this . _createPresence ( request ) ;
397
- if ( ! util . supportsPresence ( types . map [ presence . t ] ) ) {
394
+ if ( presence . t && ! util . supportsPresence ( types . map [ presence . t ] ) ) {
398
395
return callback ( { code : 9999 , message : 'Type does not support presence: ' + presence . t } ) ;
399
396
}
400
- return this . _broadcastPresence ( request . c , request . d , presence , callback ) ;
397
+ return this . _broadcastPresence ( presence , callback ) ;
398
+ case 'ps' :
399
+ return this . _subscribePresence ( request . ch , request . seq , callback ) ;
400
+ case 'pu' :
401
+ return this . _unsubscribePresence ( request . ch , request . seq , callback ) ;
401
402
default :
402
403
callback ( { code : 4000 , message : 'Invalid or unknown message' } ) ;
403
404
}
@@ -666,77 +667,82 @@ Agent.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp,
666
667
this . backend . fetchSnapshotByTimestamp ( this , collection , id , timestamp , callback ) ;
667
668
} ;
668
669
669
- Agent . prototype . _broadcastPresence = function ( collection , id , presence , callback ) {
670
- var wantsSubscribe = presence . s ;
671
- this . _handlePresenceSubscription ( collection , id , wantsSubscribe , function ( error ) {
670
+ Agent . prototype . _broadcastPresence = function ( presence , callback ) {
671
+ this . backend . transformPresenceToLatestVersion ( this , presence , function ( error , presence ) {
672
672
if ( error ) return callback ( error ) ;
673
- this . backend . transformPresenceToLatestVersion ( this , presence , function ( error , presence ) {
673
+ var channel = this . _getPresenceChannel ( presence . ch ) ;
674
+ this . backend . pubsub . publish ( [ channel ] , presence , function ( error ) {
674
675
if ( error ) return callback ( error ) ;
675
- var channel = this . backend . getPresenceChannel ( collection , id ) ;
676
- this . backend . pubsub . publish ( [ channel ] , presence , function ( error ) {
677
- if ( error ) return callback ( error ) ;
678
- callback ( null , presence ) ;
679
- } ) ;
680
- } . bind ( this ) ) ;
681
- } . bind ( this ) ) ;
682
- } ;
683
-
684
- Agent . prototype . _handlePresenceSubscription = function ( collection , id , wantsSubscribe , callback ) {
685
- var streams = this . subscribedPresences [ collection ] || ( this . subscribedPresences [ collection ] = { } ) ;
686
- var stream = streams [ id ] ;
687
-
688
- if ( stream ) {
689
- if ( wantsSubscribe ) {
690
- return callback ( ) ;
691
- }
692
- stream . destroy ( ) ;
693
- return callback ( ) ;
694
- }
695
-
696
- if ( ! wantsSubscribe ) return callback ( ) ;
697
-
698
- var channel = this . backend . getPresenceChannel ( collection , id ) ;
699
- this . backend . pubsub . subscribe ( channel , function ( error , stream ) {
700
- if ( error ) return callback ( error ) ;
701
- streams [ id ] = stream ;
702
- this . _subscribeToPresenceStream ( collection , id , stream ) ;
703
- callback ( ) ;
676
+ callback ( null , presence ) ;
677
+ } ) ;
704
678
} . bind ( this ) ) ;
705
679
} ;
706
680
707
681
Agent . prototype . _createPresence = function ( request ) {
708
- // src can be provided if it is not the same as the current agent,
709
- // such as a resubmission after a reconnect, but it usually isn't needed
710
- var src = request . src || this . clientId ;
711
682
return {
712
683
a : 'p' ,
713
- src : src ,
684
+ ch : request . ch ,
685
+ src : this . clientId ,
714
686
seq : request . seq ,
687
+ id : request . id ,
688
+ p : request . p ,
715
689
c : request . c ,
716
690
d : request . d ,
717
- id : request . id ,
718
691
v : request . v ,
719
- p : request . p ,
720
- t : request . t ,
721
- r : ! ! request . r ,
722
- s : ! ! request . s
692
+ t : request . t
723
693
} ;
724
694
} ;
725
695
696
+ Agent . prototype . _subscribePresence = function ( channel , seq , callback ) {
697
+ var presenceChannel = this . _getPresenceChannel ( channel ) ;
698
+ this . backend . pubsub . subscribe ( presenceChannel , function ( error , stream ) {
699
+ if ( error ) return callback ( error ) ;
700
+ if ( seq < this . presenceSubscriptionSeq ) return callback ( null , { ch : channel , seq : seq } ) ;
701
+ this . presenceSubscriptionSeq = seq ;
702
+ this . subscribedPresences [ channel ] = stream ;
703
+ this . _subscribeToPresenceStream ( channel , stream ) ;
704
+ this . _requestPresence ( channel , function ( error ) {
705
+ callback ( error , { ch : channel , seq : seq } ) ;
706
+ } ) ;
707
+ } . bind ( this ) ) ;
708
+ } ;
709
+
710
+ Agent . prototype . _unsubscribePresence = function ( channel , seq , callback ) {
711
+ if ( seq < this . presenceSubscriptionSeq ) return ;
712
+ this . presenceSubscriptionSeq = seq ;
713
+ var stream = this . subscribedPresences [ channel ] ;
714
+ if ( stream ) stream . destroy ( ) ;
715
+ callback ( null , { ch : channel , seq : seq } ) ;
716
+ } ;
717
+
718
+ Agent . prototype . _getPresenceChannel = function ( channel ) {
719
+ // TODO: May need to namespace this further if we want to have automatic Doc channels that don't
720
+ // clash with arbitrary user input (eg if a user decides to name their channel the same as a doc collection)
721
+ // TODO: What if a user creates a collection called _presence?
722
+ return '_presence.' + channel ;
723
+ } ;
724
+
725
+ Agent . prototype . _requestPresence = function ( channel , callback ) {
726
+ var presenceChannel = this . _getPresenceChannel ( channel ) ;
727
+ this . backend . pubsub . publish ( [ presenceChannel ] , { ch : channel , r : true , src : this . clientId } , callback ) ;
728
+ } ;
729
+
726
730
Agent . prototype . _handlePresenceData = function ( presence ) {
727
- if ( presence . src !== this . clientId ) {
728
- var backend = this . backend ;
729
- var context = {
730
- collection : presence . c ,
731
- presence : presence
732
- } ;
733
- backend . trigger ( backend . MIDDLEWARE_ACTIONS . sendPresence , this , context , function ( error ) {
734
- if ( error ) {
735
- return this . send ( { a : 'p' , c : presence . c , d : presence . d , id : presence . id , error : getReplyErrorObject ( error ) } ) ;
736
- }
737
- this . send ( presence ) ;
738
- } . bind ( this ) ) ;
739
- }
731
+ if ( presence . src === this . clientId ) return ;
732
+
733
+ if ( presence . r ) return this . send ( { a : 'pr' , ch : presence . ch } ) ;
734
+
735
+ var backend = this . backend ;
736
+ var context = {
737
+ collection : presence . c ,
738
+ presence : presence
739
+ } ;
740
+ backend . trigger ( backend . MIDDLEWARE_ACTIONS . sendPresence , this , context , function ( error ) {
741
+ if ( error ) {
742
+ return this . send ( { a : 'p' , ch : presence . ch , id : presence . id , error : getReplyErrorObject ( error ) } ) ;
743
+ }
744
+ this . send ( presence ) ;
745
+ } . bind ( this ) ) ;
740
746
} ;
741
747
742
748
function createClientOp ( request , clientId ) {
0 commit comments